michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebGLContext.h" michael@0: michael@0: #include "GLContext.h" michael@0: #include "mozilla/CheckedInt.h" michael@0: #include "WebGLBuffer.h" michael@0: #include "WebGLContextUtils.h" michael@0: #include "WebGLFramebuffer.h" michael@0: #include "WebGLProgram.h" michael@0: #include "WebGLRenderbuffer.h" michael@0: #include "WebGLShader.h" michael@0: #include "WebGLTexture.h" michael@0: #include "WebGLUniformInfo.h" michael@0: #include "WebGLVertexArray.h" michael@0: #include "WebGLVertexAttribData.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::gl; michael@0: michael@0: // For a Tegra workaround. michael@0: static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100; michael@0: michael@0: bool michael@0: WebGLContext::DrawInstanced_check(const char* info) michael@0: { michael@0: // This restriction was removed in GLES3, so WebGL2 shouldn't have it. michael@0: if (!IsWebGL2() && michael@0: IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays) && michael@0: !mBufferFetchingHasPerVertex) michael@0: { michael@0: /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt michael@0: * If all of the enabled vertex attribute arrays that are bound to active michael@0: * generic attributes in the program have a non-zero divisor, the draw michael@0: * call should return INVALID_OPERATION. michael@0: * michael@0: * NB: This also appears to apply to NV_instanced_arrays, though the michael@0: * INVALID_OPERATION emission is not explicitly stated. michael@0: * ARB_instanced_arrays does not have this restriction. michael@0: */ michael@0: ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool WebGLContext::DrawArrays_check(GLint first, GLsizei count, GLsizei primcount, const char* info) michael@0: { michael@0: if (first < 0 || count < 0) { michael@0: ErrorInvalidValue("%s: negative first or count", info); michael@0: return false; michael@0: } michael@0: michael@0: if (primcount < 0) { michael@0: ErrorInvalidValue("%s: negative primcount", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!ValidateStencilParamsForDrawCall()) { michael@0: return false; michael@0: } michael@0: michael@0: // If count is 0, there's nothing to do. michael@0: if (count == 0 || primcount == 0) { michael@0: return false; michael@0: } michael@0: michael@0: // Any checks below this depend on a program being available. michael@0: if (!mCurrentProgram) { michael@0: ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!ValidateBufferFetching(info)) { michael@0: return false; michael@0: } michael@0: michael@0: CheckedInt checked_firstPlusCount = CheckedInt(first) + count; michael@0: michael@0: if (!checked_firstPlusCount.isValid()) { michael@0: ErrorInvalidOperation("%s: overflow in first+count", info); michael@0: return false; michael@0: } michael@0: michael@0: if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) { michael@0: ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient size for given first and count", info); michael@0: return false; michael@0: } michael@0: michael@0: if (uint32_t(primcount) > mMaxFetchedInstances) { michael@0: ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info); michael@0: return false; michael@0: } michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: if (mBoundFramebuffer) { michael@0: if (!mBoundFramebuffer->CheckAndInitializeAttachments()) { michael@0: ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info); michael@0: return false; michael@0: } michael@0: } else { michael@0: ClearBackbufferIfNeeded(); michael@0: } michael@0: michael@0: if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) { michael@0: return false; michael@0: } michael@0: BindFakeBlackTextures(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei count) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateDrawModeEnum(mode, "drawArrays: mode")) michael@0: return; michael@0: michael@0: if (!DrawArrays_check(first, count, 1, "drawArrays")) michael@0: return; michael@0: michael@0: SetupContextLossTimer(); michael@0: gl->fDrawArrays(mode, first, count); michael@0: michael@0: Draw_cleanup(); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateDrawModeEnum(mode, "drawArraysInstanced: mode")) michael@0: return; michael@0: michael@0: if (!DrawArrays_check(first, count, primcount, "drawArraysInstanced")) michael@0: return; michael@0: michael@0: if (!DrawInstanced_check("drawArraysInstanced")) michael@0: return; michael@0: michael@0: SetupContextLossTimer(); michael@0: gl->fDrawArraysInstanced(mode, first, count, primcount); michael@0: michael@0: Draw_cleanup(); michael@0: } michael@0: michael@0: bool michael@0: WebGLContext::DrawElements_check(GLsizei count, GLenum type, michael@0: WebGLintptr byteOffset, GLsizei primcount, michael@0: const char* info, GLuint* out_upperBound) michael@0: { michael@0: if (count < 0 || byteOffset < 0) { michael@0: ErrorInvalidValue("%s: negative count or offset", info); michael@0: return false; michael@0: } michael@0: michael@0: if (primcount < 0) { michael@0: ErrorInvalidValue("%s: negative primcount", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!ValidateStencilParamsForDrawCall()) { michael@0: return false; michael@0: } michael@0: michael@0: // If count is 0, there's nothing to do. michael@0: if (count == 0 || primcount == 0) { michael@0: return false; michael@0: } michael@0: michael@0: CheckedUint32 checked_byteCount; michael@0: michael@0: GLsizei first = 0; michael@0: michael@0: if (type == LOCAL_GL_UNSIGNED_SHORT) { michael@0: checked_byteCount = 2 * CheckedUint32(count); michael@0: if (byteOffset % 2 != 0) { michael@0: ErrorInvalidOperation("%s: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)", info); michael@0: return false; michael@0: } michael@0: first = byteOffset / 2; michael@0: } michael@0: else if (type == LOCAL_GL_UNSIGNED_BYTE) { michael@0: checked_byteCount = count; michael@0: first = byteOffset; michael@0: } michael@0: else if (type == LOCAL_GL_UNSIGNED_INT && IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) { michael@0: checked_byteCount = 4 * CheckedUint32(count); michael@0: if (byteOffset % 4 != 0) { michael@0: ErrorInvalidOperation("%s: invalid byteOffset for UNSIGNED_INT (must be a multiple of 4)", info); michael@0: return false; michael@0: } michael@0: first = byteOffset / 4; michael@0: } michael@0: else { michael@0: ErrorInvalidEnum("%s: type must be UNSIGNED_SHORT or UNSIGNED_BYTE", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!checked_byteCount.isValid()) { michael@0: ErrorInvalidValue("%s: overflow in byteCount", info); michael@0: return false; michael@0: } michael@0: michael@0: // Any checks below this depend on a program being available. michael@0: if (!mCurrentProgram) { michael@0: ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!mBoundVertexArray->mBoundElementArrayBuffer) { michael@0: ErrorInvalidOperation("%s: must have element array buffer binding", info); michael@0: return false; michael@0: } michael@0: michael@0: WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mBoundElementArrayBuffer; michael@0: michael@0: if (!elemArrayBuffer.ByteLength()) { michael@0: ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info); michael@0: return false; michael@0: } michael@0: michael@0: CheckedInt checked_neededByteCount = checked_byteCount.toChecked() + byteOffset; michael@0: michael@0: if (!checked_neededByteCount.isValid()) { michael@0: ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info); michael@0: return false; michael@0: } michael@0: michael@0: if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) { michael@0: ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info); michael@0: return false; michael@0: } michael@0: michael@0: if (!ValidateBufferFetching(info)) michael@0: return false; michael@0: michael@0: if (!mMaxFetchedVertices || michael@0: !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, count, out_upperBound)) michael@0: { michael@0: ErrorInvalidOperation( michael@0: "%s: bound vertex attribute buffers do not have sufficient " michael@0: "size for given indices from the bound element array", info); michael@0: return false; michael@0: } michael@0: michael@0: if (uint32_t(primcount) > mMaxFetchedInstances) { michael@0: ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info); michael@0: return false; michael@0: } michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: if (mBoundFramebuffer) { michael@0: if (!mBoundFramebuffer->CheckAndInitializeAttachments()) { michael@0: ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info); michael@0: return false; michael@0: } michael@0: } else { michael@0: ClearBackbufferIfNeeded(); michael@0: } michael@0: michael@0: if (!DoFakeVertexAttrib0(mMaxFetchedVertices)) { michael@0: return false; michael@0: } michael@0: BindFakeBlackTextures(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type, michael@0: WebGLintptr byteOffset) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateDrawModeEnum(mode, "drawElements: mode")) michael@0: return; michael@0: michael@0: GLuint upperBound = UINT_MAX; michael@0: if (!DrawElements_check(count, type, byteOffset, 1, "drawElements", michael@0: &upperBound)) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: SetupContextLossTimer(); michael@0: michael@0: if (gl->IsSupported(gl::GLFeature::draw_range_elements)) { michael@0: gl->fDrawRangeElements(mode, 0, upperBound, michael@0: count, type, reinterpret_cast(byteOffset)); michael@0: } else { michael@0: gl->fDrawElements(mode, count, type, reinterpret_cast(byteOffset)); michael@0: } michael@0: michael@0: Draw_cleanup(); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, michael@0: WebGLintptr byteOffset, GLsizei primcount) michael@0: { michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: if (!ValidateDrawModeEnum(mode, "drawElementsInstanced: mode")) michael@0: return; michael@0: michael@0: if (!DrawElements_check(count, type, byteOffset, primcount, "drawElementsInstanced")) michael@0: return; michael@0: michael@0: if (!DrawInstanced_check("drawElementsInstanced")) michael@0: return; michael@0: michael@0: SetupContextLossTimer(); michael@0: gl->fDrawElementsInstanced(mode, count, type, reinterpret_cast(byteOffset), primcount); michael@0: michael@0: Draw_cleanup(); michael@0: } michael@0: michael@0: void WebGLContext::Draw_cleanup() michael@0: { michael@0: UndoFakeVertexAttrib0(); michael@0: UnbindFakeBlackTextures(); michael@0: michael@0: if (!mBoundFramebuffer) { michael@0: Invalidate(); michael@0: mShouldPresent = true; michael@0: MOZ_ASSERT(!mBackbufferNeedsClear); michael@0: } michael@0: michael@0: if (gl->WorkAroundDriverBugs()) { michael@0: if (gl->Renderer() == gl::GLRenderer::Tegra) { michael@0: mDrawCallsSinceLastFlush++; michael@0: michael@0: if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) { michael@0: gl->fFlush(); michael@0: mDrawCallsSinceLastFlush = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Let's check the viewport michael@0: const WebGLRectangleObject* rect = CurValidFBRectObject(); michael@0: if (rect) { michael@0: if (mViewportWidth > rect->Width() || michael@0: mViewportHeight > rect->Height()) michael@0: { michael@0: if (!mAlreadyWarnedAboutViewportLargerThanDest) { michael@0: GenerateWarning("Drawing to a destination rect smaller than the viewport rect. " michael@0: "(This warning will only be given once)"); michael@0: mAlreadyWarnedAboutViewportLargerThanDest = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount) michael@0: * that will be legal to be read from bound VBOs. michael@0: */ michael@0: michael@0: bool michael@0: WebGLContext::ValidateBufferFetching(const char *info) michael@0: { michael@0: #ifdef DEBUG michael@0: GLint currentProgram = 0; michael@0: MakeContextCurrent(); michael@0: gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, ¤tProgram); michael@0: MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->GLName(), michael@0: "WebGL: current program doesn't agree with GL state"); michael@0: #endif michael@0: michael@0: if (mBufferFetchingIsVerified) { michael@0: return true; michael@0: } michael@0: michael@0: bool hasPerVertex = false; michael@0: uint32_t maxVertices = UINT32_MAX; michael@0: uint32_t maxInstances = UINT32_MAX; michael@0: uint32_t attribs = mBoundVertexArray->mAttribs.Length(); michael@0: michael@0: for (uint32_t i = 0; i < attribs; ++i) { michael@0: const WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[i]; michael@0: michael@0: // If the attrib array isn't enabled, there's nothing to check; michael@0: // it's a static value. michael@0: if (!vd.enabled) michael@0: continue; michael@0: michael@0: if (vd.buf == nullptr) { michael@0: ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %d!", info, i); michael@0: return false; michael@0: } michael@0: michael@0: // If the attrib is not in use, then we don't have to validate michael@0: // it, just need to make sure that the binding is non-null. michael@0: if (!mCurrentProgram->IsAttribInUse(i)) michael@0: continue; michael@0: michael@0: // the base offset michael@0: CheckedUint32 checked_byteLength = CheckedUint32(vd.buf->ByteLength()) - vd.byteOffset; michael@0: CheckedUint32 checked_sizeOfLastElement = CheckedUint32(vd.componentSize()) * vd.size; michael@0: michael@0: if (!checked_byteLength.isValid() || michael@0: !checked_sizeOfLastElement.isValid()) michael@0: { michael@0: ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i); michael@0: return false; michael@0: } michael@0: michael@0: if (checked_byteLength.value() < checked_sizeOfLastElement.value()) { michael@0: maxVertices = 0; michael@0: maxInstances = 0; michael@0: break; michael@0: } michael@0: michael@0: CheckedUint32 checked_maxAllowedCount = ((checked_byteLength - checked_sizeOfLastElement) / vd.actualStride()) + 1; michael@0: michael@0: if (!checked_maxAllowedCount.isValid()) { michael@0: ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i); michael@0: return false; michael@0: } michael@0: michael@0: if (vd.divisor == 0) { michael@0: maxVertices = std::min(maxVertices, checked_maxAllowedCount.value()); michael@0: hasPerVertex = true; michael@0: } else { michael@0: maxInstances = std::min(maxInstances, checked_maxAllowedCount.value() / vd.divisor); michael@0: } michael@0: } michael@0: michael@0: mBufferFetchingIsVerified = true; michael@0: mBufferFetchingHasPerVertex = hasPerVertex; michael@0: mMaxFetchedVertices = maxVertices; michael@0: mMaxFetchedInstances = maxInstances; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: WebGLVertexAttrib0Status michael@0: WebGLContext::WhatDoesVertexAttrib0Need() michael@0: { michael@0: MOZ_ASSERT(mCurrentProgram); michael@0: michael@0: // work around Mac OSX crash, see bug 631420 michael@0: #ifdef XP_MACOSX michael@0: if (gl->WorkAroundDriverBugs() && michael@0: mBoundVertexArray->IsAttribArrayEnabled(0) && michael@0: !mCurrentProgram->IsAttribInUse(0)) michael@0: { michael@0: return WebGLVertexAttrib0Status::EmulatedUninitializedArray; michael@0: } michael@0: #endif michael@0: michael@0: if (MOZ_LIKELY(gl->IsGLES() || michael@0: mBoundVertexArray->IsAttribArrayEnabled(0))) michael@0: { michael@0: return WebGLVertexAttrib0Status::Default; michael@0: } michael@0: michael@0: return mCurrentProgram->IsAttribInUse(0) michael@0: ? WebGLVertexAttrib0Status::EmulatedInitializedArray michael@0: : WebGLVertexAttrib0Status::EmulatedUninitializedArray; michael@0: } michael@0: michael@0: bool michael@0: WebGLContext::DoFakeVertexAttrib0(GLuint vertexCount) michael@0: { michael@0: WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need(); michael@0: michael@0: if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default)) michael@0: return true; michael@0: michael@0: if (!mAlreadyWarnedAboutFakeVertexAttrib0) { michael@0: GenerateWarning("Drawing without vertex attrib 0 array enabled forces the browser " michael@0: "to do expensive emulation work when running on desktop OpenGL " michael@0: "platforms, for example on Mac. It is preferable to always draw " michael@0: "with vertex attrib 0 array enabled, by using bindAttribLocation " michael@0: "to bind some always-used attribute to location 0."); michael@0: mAlreadyWarnedAboutFakeVertexAttrib0 = true; michael@0: } michael@0: michael@0: CheckedUint32 checked_dataSize = CheckedUint32(vertexCount) * 4 * sizeof(GLfloat); michael@0: michael@0: if (!checked_dataSize.isValid()) { michael@0: ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0 array for a draw-operation " michael@0: "with %d vertices. Try reducing the number of vertices.", vertexCount); michael@0: return false; michael@0: } michael@0: michael@0: GLuint dataSize = checked_dataSize.value(); michael@0: michael@0: if (!mFakeVertexAttrib0BufferObject) { michael@0: gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject); michael@0: } michael@0: michael@0: // if the VBO status is already exactly what we need, or if the only difference is that it's initialized and michael@0: // we don't need it to be, then consider it OK michael@0: bool vertexAttrib0BufferStatusOK = michael@0: mFakeVertexAttrib0BufferStatus == whatDoesAttrib0Need || michael@0: (mFakeVertexAttrib0BufferStatus == WebGLVertexAttrib0Status::EmulatedInitializedArray && michael@0: whatDoesAttrib0Need == WebGLVertexAttrib0Status::EmulatedUninitializedArray); michael@0: michael@0: if (!vertexAttrib0BufferStatusOK || michael@0: mFakeVertexAttrib0BufferObjectSize < dataSize || michael@0: mFakeVertexAttrib0BufferObjectVector[0] != mVertexAttrib0Vector[0] || michael@0: mFakeVertexAttrib0BufferObjectVector[1] != mVertexAttrib0Vector[1] || michael@0: mFakeVertexAttrib0BufferObjectVector[2] != mVertexAttrib0Vector[2] || michael@0: mFakeVertexAttrib0BufferObjectVector[3] != mVertexAttrib0Vector[3]) michael@0: { michael@0: mFakeVertexAttrib0BufferStatus = whatDoesAttrib0Need; michael@0: mFakeVertexAttrib0BufferObjectSize = dataSize; michael@0: mFakeVertexAttrib0BufferObjectVector[0] = mVertexAttrib0Vector[0]; michael@0: mFakeVertexAttrib0BufferObjectVector[1] = mVertexAttrib0Vector[1]; michael@0: mFakeVertexAttrib0BufferObjectVector[2] = mVertexAttrib0Vector[2]; michael@0: mFakeVertexAttrib0BufferObjectVector[3] = mVertexAttrib0Vector[3]; michael@0: michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject); michael@0: michael@0: GetAndFlushUnderlyingGLErrors(); michael@0: michael@0: if (mFakeVertexAttrib0BufferStatus == WebGLVertexAttrib0Status::EmulatedInitializedArray) { michael@0: nsAutoArrayPtr array(new GLfloat[4 * vertexCount]); michael@0: for(size_t i = 0; i < vertexCount; ++i) { michael@0: array[4 * i + 0] = mVertexAttrib0Vector[0]; michael@0: array[4 * i + 1] = mVertexAttrib0Vector[1]; michael@0: array[4 * i + 2] = mVertexAttrib0Vector[2]; michael@0: array[4 * i + 3] = mVertexAttrib0Vector[3]; michael@0: } michael@0: gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, array, LOCAL_GL_DYNAMIC_DRAW); michael@0: } else { michael@0: gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW); michael@0: } michael@0: GLenum error = GetAndFlushUnderlyingGLErrors(); michael@0: michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0); michael@0: michael@0: // note that we do this error checking and early return AFTER having restored the buffer binding above michael@0: if (error) { michael@0: ErrorOutOfMemory("Ran out of memory trying to construct a fake vertex attrib 0 array for a draw-operation " michael@0: "with %d vertices. Try reducing the number of vertices.", vertexCount); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject); michael@0: gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::UndoFakeVertexAttrib0() michael@0: { michael@0: WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need(); michael@0: michael@0: if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default)) michael@0: return; michael@0: michael@0: if (mBoundVertexArray->HasAttrib(0) && mBoundVertexArray->mAttribs[0].buf) { michael@0: const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0]; michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.buf->GLName()); michael@0: gl->fVertexAttribPointer(0, michael@0: attrib0.size, michael@0: attrib0.type, michael@0: attrib0.normalized, michael@0: attrib0.stride, michael@0: reinterpret_cast(attrib0.byteOffset)); michael@0: } else { michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); michael@0: } michael@0: michael@0: gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0); michael@0: } michael@0: michael@0: WebGLContextFakeBlackStatus michael@0: WebGLContext::ResolvedFakeBlackStatus() michael@0: { michael@0: // handle this case first, it's the generic case michael@0: if (MOZ_LIKELY(mFakeBlackStatus == WebGLContextFakeBlackStatus::NotNeeded)) michael@0: return mFakeBlackStatus; michael@0: michael@0: if (mFakeBlackStatus == WebGLContextFakeBlackStatus::Needed) michael@0: return mFakeBlackStatus; michael@0: michael@0: for (int32_t i = 0; i < mGLMaxTextureUnits; ++i) { michael@0: if ((mBound2DTextures[i] && mBound2DTextures[i]->ResolvedFakeBlackStatus() != WebGLTextureFakeBlackStatus::NotNeeded) || michael@0: (mBoundCubeMapTextures[i] && mBoundCubeMapTextures[i]->ResolvedFakeBlackStatus() != WebGLTextureFakeBlackStatus::NotNeeded)) michael@0: { michael@0: mFakeBlackStatus = WebGLContextFakeBlackStatus::Needed; michael@0: return mFakeBlackStatus; michael@0: } michael@0: } michael@0: michael@0: // we have exhausted all cases where we do need fakeblack, so if the status is still unknown, michael@0: // that means that we do NOT need it. michael@0: mFakeBlackStatus = WebGLContextFakeBlackStatus::NotNeeded; michael@0: return mFakeBlackStatus; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BindFakeBlackTexturesHelper( michael@0: GLenum target, michael@0: const nsTArray > & boundTexturesArray, michael@0: ScopedDeletePtr & opaqueTextureScopedPtr, michael@0: ScopedDeletePtr & transparentTextureScopedPtr) michael@0: { michael@0: for (int32_t i = 0; i < mGLMaxTextureUnits; ++i) { michael@0: if (!boundTexturesArray[i]) { michael@0: continue; michael@0: } michael@0: michael@0: WebGLTextureFakeBlackStatus s = boundTexturesArray[i]->ResolvedFakeBlackStatus(); michael@0: MOZ_ASSERT(s != WebGLTextureFakeBlackStatus::Unknown); michael@0: michael@0: if (MOZ_LIKELY(s == WebGLTextureFakeBlackStatus::NotNeeded)) { michael@0: continue; michael@0: } michael@0: michael@0: bool alpha = s == WebGLTextureFakeBlackStatus::UninitializedImageData && michael@0: FormatHasAlpha(boundTexturesArray[i]->ImageInfoBase().WebGLFormat()); michael@0: ScopedDeletePtr& michael@0: blackTexturePtr = alpha michael@0: ? transparentTextureScopedPtr michael@0: : opaqueTextureScopedPtr; michael@0: michael@0: if (!blackTexturePtr) { michael@0: GLenum format = alpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB; michael@0: blackTexturePtr michael@0: = new FakeBlackTexture(gl, target, format); michael@0: } michael@0: michael@0: gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); michael@0: gl->fBindTexture(target, michael@0: blackTexturePtr->GLName()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::BindFakeBlackTextures() michael@0: { michael@0: // this is the generic case: try to return early michael@0: if (MOZ_LIKELY(ResolvedFakeBlackStatus() == WebGLContextFakeBlackStatus::NotNeeded)) michael@0: return; michael@0: michael@0: BindFakeBlackTexturesHelper(LOCAL_GL_TEXTURE_2D, michael@0: mBound2DTextures, michael@0: mBlackOpaqueTexture2D, michael@0: mBlackTransparentTexture2D); michael@0: BindFakeBlackTexturesHelper(LOCAL_GL_TEXTURE_CUBE_MAP, michael@0: mBoundCubeMapTextures, michael@0: mBlackOpaqueTextureCubeMap, michael@0: mBlackTransparentTextureCubeMap); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::UnbindFakeBlackTextures() michael@0: { michael@0: // this is the generic case: try to return early michael@0: if (MOZ_LIKELY(ResolvedFakeBlackStatus() == WebGLContextFakeBlackStatus::NotNeeded)) michael@0: return; michael@0: michael@0: for (int32_t i = 0; i < mGLMaxTextureUnits; ++i) { michael@0: if (mBound2DTextures[i] && mBound2DTextures[i]->ResolvedFakeBlackStatus() != WebGLTextureFakeBlackStatus::NotNeeded) { michael@0: gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); michael@0: gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mBound2DTextures[i]->GLName()); michael@0: } michael@0: if (mBoundCubeMapTextures[i] && mBoundCubeMapTextures[i]->ResolvedFakeBlackStatus() != WebGLTextureFakeBlackStatus::NotNeeded) { michael@0: gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); michael@0: gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, mBoundCubeMapTextures[i]->GLName()); michael@0: } michael@0: } michael@0: michael@0: gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture); michael@0: } michael@0: michael@0: WebGLContext::FakeBlackTexture::FakeBlackTexture(GLContext *gl, GLenum target, GLenum format) michael@0: : mGL(gl) michael@0: , mGLName(0) michael@0: { michael@0: MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D || target == LOCAL_GL_TEXTURE_CUBE_MAP); michael@0: MOZ_ASSERT(format == LOCAL_GL_RGB || format == LOCAL_GL_RGBA); michael@0: michael@0: mGL->MakeCurrent(); michael@0: GLuint formerBinding = 0; michael@0: gl->GetUIntegerv(target == LOCAL_GL_TEXTURE_2D michael@0: ? LOCAL_GL_TEXTURE_BINDING_2D michael@0: : LOCAL_GL_TEXTURE_BINDING_CUBE_MAP, michael@0: &formerBinding); michael@0: gl->fGenTextures(1, &mGLName); michael@0: gl->fBindTexture(target, mGLName); michael@0: michael@0: // we allocate our zeros on the heap, and we overallocate (16 bytes instead of 4) michael@0: // to minimize the risk of running into a driver bug in texImage2D, as it is michael@0: // a bit unusual maybe to create 1x1 textures, and the stack may not have the alignment michael@0: // that texImage2D expects. michael@0: void* zeros = calloc(1, 16); michael@0: if (target == LOCAL_GL_TEXTURE_2D) { michael@0: gl->fTexImage2D(target, 0, format, 1, 1, michael@0: 0, format, LOCAL_GL_UNSIGNED_BYTE, zeros); michael@0: } else { michael@0: for (GLuint i = 0; i < 6; ++i) { michael@0: gl->fTexImage2D(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, 1, 1, michael@0: 0, format, LOCAL_GL_UNSIGNED_BYTE, zeros); michael@0: } michael@0: } michael@0: free(zeros); michael@0: michael@0: gl->fBindTexture(target, formerBinding); michael@0: } michael@0: michael@0: WebGLContext::FakeBlackTexture::~FakeBlackTexture() michael@0: { michael@0: if (mGL) { michael@0: mGL->MakeCurrent(); michael@0: mGL->fDeleteTextures(1, &mGLName); michael@0: } michael@0: }