summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLContextDraw.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/WebGLContextDraw.cpp')
-rw-r--r--dom/canvas/WebGLContextDraw.cpp1256
1 files changed, 1256 insertions, 0 deletions
diff --git a/dom/canvas/WebGLContextDraw.cpp b/dom/canvas/WebGLContextDraw.cpp
new file mode 100644
index 000000000..66fca7689
--- /dev/null
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -0,0 +1,1256 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContext.h"
+
+#include "GLContext.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsPrintfCString.h"
+#include "WebGLBuffer.h"
+#include "WebGLContextUtils.h"
+#include "WebGLFramebuffer.h"
+#include "WebGLProgram.h"
+#include "WebGLRenderbuffer.h"
+#include "WebGLShader.h"
+#include "WebGLTexture.h"
+#include "WebGLVertexArray.h"
+#include "WebGLVertexAttribData.h"
+
+#include <algorithm>
+
+namespace mozilla {
+
+// For a Tegra workaround.
+static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
+
+////////////////////////////////////////
+
+class ScopedResolveTexturesForDraw
+{
+ struct TexRebindRequest
+ {
+ uint32_t texUnit;
+ WebGLTexture* tex;
+ };
+
+ WebGLContext* const mWebGL;
+ std::vector<TexRebindRequest> mRebindRequests;
+
+public:
+ ScopedResolveTexturesForDraw(WebGLContext* webgl, const char* funcName,
+ bool* const out_error);
+ ~ScopedResolveTexturesForDraw();
+};
+
+bool
+WebGLTexture::IsFeedback(WebGLContext* webgl, const char* funcName, uint32_t texUnit,
+ const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const
+{
+ auto itr = fbAttachments.cbegin();
+ for (; itr != fbAttachments.cend(); ++itr) {
+ const auto& attach = *itr;
+ if (attach->Texture() == this)
+ break;
+ }
+
+ if (itr == fbAttachments.cend())
+ return false;
+
+ ////
+
+ const auto minLevel = mBaseMipmapLevel;
+ uint32_t maxLevel;
+ if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) {
+ // No valid mips. Will need fake-black.
+ return false;
+ }
+
+ ////
+
+ for (; itr != fbAttachments.cend(); ++itr) {
+ const auto& attach = *itr;
+ if (attach->Texture() != this)
+ continue;
+
+ const auto dstLevel = attach->MipLevel();
+
+ if (minLevel <= dstLevel && dstLevel <= maxLevel) {
+ webgl->ErrorInvalidOperation("%s: Feedback loop detected between tex target"
+ " 0x%04x, tex unit %u, levels %u-%u; and"
+ " framebuffer attachment 0x%04x, level %u.",
+ funcName, mTarget.get(), texUnit, minLevel,
+ maxLevel, attach->mAttachmentPoint, dstLevel);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
+ const char* funcName,
+ bool* const out_error)
+ : mWebGL(webgl)
+{
+ MOZ_ASSERT(mWebGL->gl->IsCurrent());
+
+ if (!mWebGL->mActiveProgramLinkInfo) {
+ mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
+ *out_error = true;
+ return;
+ }
+
+ const std::vector<const WebGLFBAttachPoint*>* attachList = nullptr;
+ const auto& fb = mWebGL->mBoundDrawFramebuffer;
+ if (fb) {
+ if (!fb->ValidateAndInitAttachments(funcName)) {
+ *out_error = true;
+ return;
+ }
+
+ attachList = &(fb->ResolvedCompleteData()->texDrawBuffers);
+ } else {
+ webgl->ClearBackbufferIfNeeded();
+ }
+
+ MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
+ const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
+ for (const auto& uniform : uniformSamplers) {
+ const auto& texList = *(uniform->mSamplerTexList);
+
+ for (const auto& texUnit : uniform->mSamplerValues) {
+ if (texUnit >= texList.Length())
+ continue;
+
+ const auto& tex = texList[texUnit];
+ if (!tex)
+ continue;
+
+ if (attachList &&
+ tex->IsFeedback(mWebGL, funcName, texUnit, *attachList))
+ {
+ *out_error = true;
+ return;
+ }
+
+ FakeBlackType fakeBlack;
+ if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
+ mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ if (fakeBlack == FakeBlackType::None)
+ continue;
+
+ if (!mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack)) {
+ mWebGL->ErrorOutOfMemory("%s: Failed to create fake black texture.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ mRebindRequests.push_back({texUnit, tex});
+ }
+ }
+
+ *out_error = false;
+}
+
+ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw()
+{
+ if (!mRebindRequests.size())
+ return;
+
+ gl::GLContext* gl = mWebGL->gl;
+
+ for (const auto& itr : mRebindRequests) {
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
+ gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
+ }
+
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
+}
+
+bool
+WebGLContext::BindFakeBlack(uint32_t texUnit, TexTarget target, FakeBlackType fakeBlack)
+{
+ MOZ_ASSERT(fakeBlack == FakeBlackType::RGBA0000 ||
+ fakeBlack == FakeBlackType::RGBA0001);
+
+ const auto fnGetSlot = [this, target, fakeBlack]() -> UniquePtr<FakeBlackTexture>*
+ {
+ switch (fakeBlack) {
+ case FakeBlackType::RGBA0000:
+ switch (target.get()) {
+ case LOCAL_GL_TEXTURE_2D : return &mFakeBlack_2D_0000;
+ case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0000;
+ case LOCAL_GL_TEXTURE_3D : return &mFakeBlack_3D_0000;
+ case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0000;
+ default: return nullptr;
+ }
+
+ case FakeBlackType::RGBA0001:
+ switch (target.get()) {
+ case LOCAL_GL_TEXTURE_2D : return &mFakeBlack_2D_0001;
+ case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0001;
+ case LOCAL_GL_TEXTURE_3D : return &mFakeBlack_3D_0001;
+ case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0001;
+ default: return nullptr;
+ }
+
+ default:
+ return nullptr;
+ }
+ };
+
+ UniquePtr<FakeBlackTexture>* slot = fnGetSlot();
+ if (!slot) {
+ MOZ_CRASH("GFX: fnGetSlot failed.");
+ }
+ UniquePtr<FakeBlackTexture>& fakeBlackTex = *slot;
+
+ if (!fakeBlackTex) {
+ fakeBlackTex = FakeBlackTexture::Create(gl, target, fakeBlack);
+ if (!fakeBlackTex) {
+ return false;
+ }
+ }
+
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
+ gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
+ return true;
+}
+
+////////////////////////////////////////
+
+bool
+WebGLContext::DrawInstanced_check(const char* info)
+{
+ MOZ_ASSERT(IsWebGL2() ||
+ IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays));
+ if (!mBufferFetchingHasPerVertex) {
+ /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt
+ * If all of the enabled vertex attribute arrays that are bound to active
+ * generic attributes in the program have a non-zero divisor, the draw
+ * call should return INVALID_OPERATION.
+ *
+ * NB: This also appears to apply to NV_instanced_arrays, though the
+ * INVALID_OPERATION emission is not explicitly stated.
+ * ARB_instanced_arrays does not have this restriction.
+ */
+ ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first,
+ GLsizei vertCount, GLsizei instanceCount)
+{
+ if (!ValidateDrawModeEnum(mode, funcName))
+ return false;
+
+ if (!ValidateNonNegative(funcName, "first", first) ||
+ !ValidateNonNegative(funcName, "vertCount", vertCount) ||
+ !ValidateNonNegative(funcName, "instanceCount", instanceCount))
+ {
+ return false;
+ }
+
+ if (!ValidateStencilParamsForDrawCall())
+ return false;
+
+ if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
+ MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
+ if (mPrimRestartTypeBytes != 0) {
+ mPrimRestartTypeBytes = 0;
+
+ // OSX appears to have severe perf issues with leaving this enabled.
+ gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
+ }
+ }
+
+ if (!vertCount || !instanceCount)
+ return false; // No error, just early out.
+
+ if (!ValidateBufferFetching(funcName))
+ return false;
+
+ const auto checked_firstPlusCount = CheckedInt<GLsizei>(first) + vertCount;
+ if (!checked_firstPlusCount.isValid()) {
+ ErrorInvalidOperation("%s: overflow in first+vertCount", funcName);
+ return false;
+ }
+
+ if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) {
+ ErrorInvalidOperation("%s: Bound vertex attribute buffers do not have sufficient"
+ " size for given first and count.",
+ funcName);
+ return false;
+ }
+
+ return true;
+}
+
+////////////////////////////////////////
+
+template<typename T>
+static bool
+DoSetsIntersect(const std::set<T>& a, const std::set<T>& b)
+{
+ std::vector<T> intersection;
+ std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
+ std::back_inserter(intersection));
+ return bool(intersection.size());
+}
+
+class ScopedDrawHelper final
+{
+ WebGLContext* const mWebGL;
+ bool mDidFake;
+
+public:
+ ScopedDrawHelper(WebGLContext* webgl, const char* funcName, uint32_t firstVertex,
+ uint32_t vertCount, uint32_t instanceCount, bool* const out_error)
+ : mWebGL(webgl)
+ , mDidFake(false)
+ {
+ if (instanceCount > mWebGL->mMaxFetchedInstances) {
+ mWebGL->ErrorInvalidOperation("%s: Bound instance attribute buffers do not"
+ " have sufficient size for given"
+ " `instanceCount`.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ MOZ_ASSERT(mWebGL->gl->IsCurrent());
+
+ if (mWebGL->mBoundDrawFramebuffer) {
+ if (!mWebGL->mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) {
+ *out_error = true;
+ return;
+ }
+ } else {
+ mWebGL->ClearBackbufferIfNeeded();
+ }
+
+ ////
+
+ const size_t requiredVerts = firstVertex + vertCount;
+ if (!mWebGL->DoFakeVertexAttrib0(funcName, requiredVerts)) {
+ *out_error = true;
+ return;
+ }
+ mDidFake = true;
+
+ ////
+ // Check UBO sizes.
+
+ const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
+
+ for (const auto& cur : linkInfo->uniformBlocks) {
+ const auto& dataSize = cur->mDataSize;
+ const auto& binding = cur->mBinding;
+ if (!binding) {
+ mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ const auto availByteCount = binding->ByteCount();
+ if (dataSize > availByteCount) {
+ mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
+ " than UNIFORM_BLOCK_DATA_SIZE.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ if (binding->mBufferBinding->IsBoundForTF()) {
+ mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is bound or"
+ " in use for transform feedback.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+ }
+
+ ////
+
+ const auto& tfo = mWebGL->mBoundTransformFeedback;
+ if (tfo && tfo->IsActiveAndNotPaused()) {
+ uint32_t numUsed;
+ switch (linkInfo->transformFeedbackBufferMode) {
+ case LOCAL_GL_INTERLEAVED_ATTRIBS:
+ numUsed = 1;
+ break;
+
+ case LOCAL_GL_SEPARATE_ATTRIBS:
+ numUsed = linkInfo->transformFeedbackVaryings.size();
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+
+ for (uint32_t i = 0; i < numUsed; ++i) {
+ const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
+ if (buffer->IsBoundForNonTF()) {
+ mWebGL->ErrorInvalidOperation("%s: Transform feedback varying %u's"
+ " buffer is bound for"
+ " non-transform-feedback.",
+ funcName, i);
+ *out_error = true;
+ return;
+ }
+ }
+ }
+
+ ////
+
+ for (const auto& progAttrib : linkInfo->attribs) {
+ const auto& loc = progAttrib.mLoc;
+ if (loc == -1)
+ continue;
+
+ const auto& attribData = mWebGL->mBoundVertexArray->mAttribs[loc];
+
+ GLenum attribDataBaseType;
+ if (attribData.mEnabled) {
+ attribDataBaseType = attribData.BaseType();
+
+ if (attribData.mBuf->IsBoundForTF()) {
+ mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u's buffer is bound"
+ " or in use for transform feedback.",
+ funcName, loc);
+ *out_error = true;
+ return;
+ }
+ } else {
+ attribDataBaseType = mWebGL->mGenericVertexAttribTypes[loc];
+ }
+
+ if (attribDataBaseType != progAttrib.mBaseType) {
+ nsCString progType, dataType;
+ WebGLContext::EnumName(progAttrib.mBaseType, &progType);
+ WebGLContext::EnumName(attribDataBaseType, &dataType);
+ mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u requires data of type"
+ " %s, but is being supplied with type %s.",
+ funcName, loc, progType.BeginReading(),
+ dataType.BeginReading());
+ *out_error = true;
+ return;
+ }
+ }
+
+ ////
+
+ mWebGL->RunContextLossTimer();
+ }
+
+ ~ScopedDrawHelper() {
+ if (mDidFake) {
+ mWebGL->UndoFakeVertexAttrib0();
+ }
+ }
+};
+
+////////////////////////////////////////
+
+static uint32_t
+UsedVertsForTFDraw(GLenum mode, uint32_t vertCount)
+{
+ uint8_t vertsPerPrim;
+
+ switch (mode) {
+ case LOCAL_GL_POINTS:
+ vertsPerPrim = 1;
+ break;
+ case LOCAL_GL_LINES:
+ vertsPerPrim = 2;
+ break;
+ case LOCAL_GL_TRIANGLES:
+ vertsPerPrim = 3;
+ break;
+ default:
+ MOZ_CRASH("`mode`");
+ }
+
+ return vertCount / vertsPerPrim * vertsPerPrim;
+}
+
+class ScopedDrawWithTransformFeedback final
+{
+ WebGLContext* const mWebGL;
+ WebGLTransformFeedback* const mTFO;
+ const bool mWithTF;
+ uint32_t mUsedVerts;
+
+public:
+ ScopedDrawWithTransformFeedback(WebGLContext* webgl, const char* funcName,
+ GLenum mode, uint32_t vertCount,
+ uint32_t instanceCount, bool* const out_error)
+ : mWebGL(webgl)
+ , mTFO(mWebGL->mBoundTransformFeedback)
+ , mWithTF(mTFO &&
+ mTFO->mIsActive &&
+ !mTFO->mIsPaused)
+ , mUsedVerts(0)
+ {
+ *out_error = false;
+ if (!mWithTF)
+ return;
+
+ if (mode != mTFO->mActive_PrimMode) {
+ mWebGL->ErrorInvalidOperation("%s: Drawing with transform feedback requires"
+ " `mode` to match BeginTransformFeedback's"
+ " `primitiveMode`.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
+ const auto usedVerts = CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
+
+ const auto remainingCapacity = mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
+ if (!usedVerts.isValid() ||
+ usedVerts.value() > remainingCapacity)
+ {
+ mWebGL->ErrorInvalidOperation("%s: Insufficient buffer capacity remaining for"
+ " transform feedback.",
+ funcName);
+ *out_error = true;
+ return;
+ }
+
+ mUsedVerts = usedVerts.value();
+ }
+
+ void Advance() const {
+ if (!mWithTF)
+ return;
+
+ mTFO->mActive_VertPosition += mUsedVerts;
+ }
+};
+
+////////////////////////////////////////
+
+void
+WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei vertCount)
+{
+ const char funcName[] = "drawArrays";
+ if (IsContextLost())
+ return;
+
+ MakeContextCurrent();
+
+ bool error = false;
+ ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+ if (error)
+ return;
+
+ const GLsizei instanceCount = 1;
+ if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+ return;
+
+ const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+ if (error)
+ return;
+
+ const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
+ instanceCount, &error);
+ if (error)
+ return;
+
+ {
+ ScopedDrawCallWrapper wrapper(*this);
+ gl->fDrawArrays(mode, first, vertCount);
+ }
+
+ Draw_cleanup(funcName);
+ scopedTF.Advance();
+}
+
+void
+WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
+ GLsizei instanceCount)
+{
+ const char funcName[] = "drawArraysInstanced";
+ if (IsContextLost())
+ return;
+
+ MakeContextCurrent();
+
+ bool error = false;
+ ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+ if (error)
+ return;
+
+ if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+ return;
+
+ if (!DrawInstanced_check(funcName))
+ return;
+
+ const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+ if (error)
+ return;
+
+ const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
+ instanceCount, &error);
+ if (error)
+ return;
+
+ {
+ ScopedDrawCallWrapper wrapper(*this);
+ gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
+ }
+
+ Draw_cleanup(funcName);
+ scopedTF.Advance();
+}
+
+////////////////////////////////////////
+
+bool
+WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
+ GLenum type, WebGLintptr byteOffset,
+ GLsizei instanceCount)
+{
+ if (!ValidateDrawModeEnum(mode, funcName))
+ return false;
+
+ if (mBoundTransformFeedback &&
+ mBoundTransformFeedback->mIsActive &&
+ !mBoundTransformFeedback->mIsPaused)
+ {
+ ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
+ " transform feedback.",
+ funcName);
+ return false;
+ }
+
+ if (!ValidateNonNegative(funcName, "vertCount", vertCount) ||
+ !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
+ !ValidateNonNegative(funcName, "instanceCount", instanceCount))
+ {
+ return false;
+ }
+
+ if (!ValidateStencilParamsForDrawCall())
+ return false;
+
+ if (!vertCount || !instanceCount)
+ return false; // No error, just early out.
+
+ uint8_t bytesPerElem = 0;
+ switch (type) {
+ case LOCAL_GL_UNSIGNED_BYTE:
+ bytesPerElem = 1;
+ break;
+
+ case LOCAL_GL_UNSIGNED_SHORT:
+ bytesPerElem = 2;
+ break;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
+ bytesPerElem = 4;
+ }
+ break;
+ }
+
+ if (!bytesPerElem) {
+ ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
+ return false;
+ }
+
+ if (byteOffset % bytesPerElem != 0) {
+ ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
+ funcName);
+ return false;
+ }
+
+ ////
+
+ if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
+ MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
+ if (mPrimRestartTypeBytes != bytesPerElem) {
+ mPrimRestartTypeBytes = bytesPerElem;
+
+ const uint32_t ones = UINT32_MAX >> (32 - 8*mPrimRestartTypeBytes);
+ gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
+ gl->fPrimitiveRestartIndex(ones);
+ }
+ }
+
+ ////
+
+ const GLsizei first = byteOffset / bytesPerElem;
+ const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(vertCount);
+
+ if (!checked_byteCount.isValid()) {
+ ErrorInvalidValue("%s: Overflow in byteCount.", funcName);
+ return false;
+ }
+
+ if (!mBoundVertexArray->mElementArrayBuffer) {
+ ErrorInvalidOperation("%s: Must have element array buffer binding.", funcName);
+ return false;
+ }
+
+ WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
+
+ if (!elemArrayBuffer.ByteLength()) {
+ ErrorInvalidOperation("%s: Bound element array buffer doesn't have any data.",
+ funcName);
+ return false;
+ }
+
+ CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
+
+ if (!checked_neededByteCount.isValid()) {
+ ErrorInvalidOperation("%s: Overflow in byteOffset+byteCount.", funcName);
+ return false;
+ }
+
+ if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) {
+ ErrorInvalidOperation("%s: Bound element array buffer is too small for given"
+ " count and offset.",
+ funcName);
+ return false;
+ }
+
+ if (!ValidateBufferFetching(funcName))
+ return false;
+
+ if (!mMaxFetchedVertices ||
+ !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, vertCount))
+ {
+ ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient "
+ "size for given indices from the bound element array",
+ funcName);
+ return false;
+ }
+
+ // Bug 1008310 - Check if buffer has been used with a different previous type
+ if (elemArrayBuffer.IsElementArrayUsedWithMultipleTypes()) {
+ nsCString typeName;
+ WebGLContext::EnumName(type, &typeName);
+ GenerateWarning("%s: bound element array buffer previously used with a type other than "
+ "%s, this will affect performance.",
+ funcName, typeName.BeginReading());
+ }
+
+ return true;
+}
+
+static void
+HandleDrawElementsErrors(WebGLContext* webgl, const char* funcName,
+ gl::GLContext::LocalErrorScope& errorScope)
+{
+ const auto err = errorScope.GetError();
+ if (err == LOCAL_GL_INVALID_OPERATION) {
+ webgl->ErrorInvalidOperation("%s: Driver rejected indexed draw call, possibly"
+ " due to out-of-bounds indices.", funcName);
+ return;
+ }
+
+ MOZ_ASSERT(!err);
+ if (err) {
+ webgl->ErrorImplementationBug("%s: Unexpected driver error during indexed draw"
+ " call. Please file a bug.",
+ funcName);
+ return;
+ }
+}
+
+void
+WebGLContext::DrawElements(GLenum mode, GLsizei vertCount, GLenum type,
+ WebGLintptr byteOffset, const char* funcName)
+{
+ if (!funcName) {
+ funcName = "drawElements";
+ }
+
+ if (IsContextLost())
+ return;
+
+ MakeContextCurrent();
+
+ bool error = false;
+ ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+ if (error)
+ return;
+
+ const GLsizei instanceCount = 1;
+ if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+ return;
+
+ const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+ &error);
+ if (error)
+ return;
+
+ {
+ ScopedDrawCallWrapper wrapper(*this);
+ {
+ UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
+
+ if (gl->IsANGLE()) {
+ errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
+ }
+
+ gl->fDrawElements(mode, vertCount, type,
+ reinterpret_cast<GLvoid*>(byteOffset));
+
+ if (errorScope) {
+ HandleDrawElementsErrors(this, funcName, *errorScope);
+ }
+ }
+ }
+
+ Draw_cleanup(funcName);
+}
+
+void
+WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei vertCount, GLenum type,
+ WebGLintptr byteOffset, GLsizei instanceCount)
+{
+ const char funcName[] = "drawElementsInstanced";
+ if (IsContextLost())
+ return;
+
+ MakeContextCurrent();
+
+ bool error = false;
+ ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+ if (error)
+ return;
+
+ if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+ return;
+
+ if (!DrawInstanced_check(funcName))
+ return;
+
+ const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+ &error);
+ if (error)
+ return;
+
+ {
+ ScopedDrawCallWrapper wrapper(*this);
+ {
+ UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
+
+ if (gl->IsANGLE()) {
+ errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
+ }
+
+ gl->fDrawElementsInstanced(mode, vertCount, type,
+ reinterpret_cast<GLvoid*>(byteOffset),
+ instanceCount);
+ if (errorScope) {
+ HandleDrawElementsErrors(this, funcName, *errorScope);
+ }
+ }
+ }
+
+ Draw_cleanup(funcName);
+}
+
+////////////////////////////////////////
+
+void
+WebGLContext::Draw_cleanup(const char* funcName)
+{
+ if (gl->WorkAroundDriverBugs()) {
+ if (gl->Renderer() == gl::GLRenderer::Tegra) {
+ mDrawCallsSinceLastFlush++;
+
+ if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
+ gl->fFlush();
+ mDrawCallsSinceLastFlush = 0;
+ }
+ }
+ }
+
+ // Let's check for a really common error: Viewport is larger than the actual
+ // destination framebuffer.
+ uint32_t destWidth = mViewportWidth;
+ uint32_t destHeight = mViewportHeight;
+
+ if (mBoundDrawFramebuffer) {
+ const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
+ for (const auto& cur : drawBuffers) {
+ if (!cur->IsDefined())
+ continue;
+ cur->Size(&destWidth, &destHeight);
+ break;
+ }
+ } else {
+ destWidth = mWidth;
+ destHeight = mHeight;
+ }
+
+ if (mViewportWidth > int32_t(destWidth) ||
+ mViewportHeight > int32_t(destHeight))
+ {
+ if (!mAlreadyWarnedAboutViewportLargerThanDest) {
+ GenerateWarning("%s: Drawing to a destination rect smaller than the viewport"
+ " rect. (This warning will only be given once)",
+ funcName);
+ mAlreadyWarnedAboutViewportLargerThanDest = true;
+ }
+ }
+}
+
+/*
+ * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount)
+ * that will be legal to be read from bound VBOs.
+ */
+
+bool
+WebGLContext::ValidateBufferFetching(const char* info)
+{
+ MOZ_ASSERT(mCurrentProgram);
+ // Note that mCurrentProgram->IsLinked() is NOT GUARANTEED.
+ MOZ_ASSERT(mActiveProgramLinkInfo);
+
+#ifdef DEBUG
+ GLint currentProgram = 0;
+ MakeContextCurrent();
+ gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &currentProgram);
+ MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->mGLName,
+ "WebGL: current program doesn't agree with GL state");
+#endif
+
+ if (mBufferFetchingIsVerified)
+ return true;
+
+ bool hasPerVertex = false;
+ uint32_t maxVertices = UINT32_MAX;
+ uint32_t maxInstances = UINT32_MAX;
+ const uint32_t attribCount = mBoundVertexArray->mAttribs.Length();
+
+ uint32_t i = 0;
+ for (const auto& vd : mBoundVertexArray->mAttribs) {
+ // If the attrib array isn't enabled, there's nothing to check;
+ // it's a static value.
+ if (!vd.mEnabled)
+ continue;
+
+ if (!vd.mBuf) {
+ ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %du!",
+ info, i);
+ return false;
+ }
+
+ ++i;
+ }
+
+ mBufferFetch_IsAttrib0Active = false;
+
+ for (const auto& attrib : mActiveProgramLinkInfo->attribs) {
+ if (attrib.mLoc == -1)
+ continue;
+
+ const uint32_t attribLoc(attrib.mLoc);
+ if (attribLoc >= attribCount)
+ continue;
+
+ if (attribLoc == 0) {
+ mBufferFetch_IsAttrib0Active = true;
+ }
+
+ const auto& vd = mBoundVertexArray->mAttribs[attribLoc];
+ if (!vd.mEnabled)
+ continue;
+
+ const auto& bufByteLen = vd.mBuf->ByteLength();
+ if (vd.ByteOffset() > bufByteLen) {
+ maxVertices = 0;
+ maxInstances = 0;
+ break;
+ }
+
+ size_t availBytes = bufByteLen - vd.ByteOffset();
+ if (vd.BytesPerVertex() > availBytes) {
+ maxVertices = 0;
+ maxInstances = 0;
+ break;
+ }
+ availBytes -= vd.BytesPerVertex();
+ const size_t vertCapacity = 1 + availBytes / vd.ExplicitStride();
+
+ if (vd.mDivisor == 0) {
+ if (vertCapacity < maxVertices) {
+ maxVertices = vertCapacity;
+ }
+ hasPerVertex = true;
+ } else {
+ const auto curMaxInstances = CheckedInt<size_t>(vertCapacity) * vd.mDivisor;
+ // If this isn't valid, it's because we overflowed, which means we can support
+ // *too much*. Don't update maxInstances in this case.
+ if (curMaxInstances.isValid() &&
+ curMaxInstances.value() < maxInstances)
+ {
+ maxInstances = curMaxInstances.value();
+ }
+ }
+ }
+
+ mBufferFetchingIsVerified = true;
+ mBufferFetchingHasPerVertex = hasPerVertex;
+ mMaxFetchedVertices = maxVertices;
+ mMaxFetchedInstances = maxInstances;
+
+ return true;
+}
+
+WebGLVertexAttrib0Status
+WebGLContext::WhatDoesVertexAttrib0Need() const
+{
+ MOZ_ASSERT(mCurrentProgram);
+ MOZ_ASSERT(mActiveProgramLinkInfo);
+
+ const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
+
+ bool legacyAttrib0 = gl->IsCompatibilityProfile();
+#ifdef XP_MACOSX
+ if (gl->WorkAroundDriverBugs()) {
+ // Failures in conformance/attribs/gl-disabled-vertex-attrib.
+ // Even in Core profiles on NV. Sigh.
+ legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
+ }
+#endif
+
+ if (!legacyAttrib0)
+ return WebGLVertexAttrib0Status::Default;
+
+ if (isAttribArray0Enabled && mBufferFetch_IsAttrib0Active)
+ return WebGLVertexAttrib0Status::Default;
+
+ if (mBufferFetch_IsAttrib0Active)
+ return WebGLVertexAttrib0Status::EmulatedInitializedArray;
+
+ // Ensure that the legacy code has enough buffer.
+ return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
+}
+
+bool
+WebGLContext::DoFakeVertexAttrib0(const char* funcName, GLuint vertexCount)
+{
+ if (!vertexCount) {
+ vertexCount = 1;
+ }
+
+ const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
+ if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
+ return true;
+
+ if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
+ GenerateWarning("Drawing without vertex attrib 0 array enabled forces the browser "
+ "to do expensive emulation work when running on desktop OpenGL "
+ "platforms, for example on Mac. It is preferable to always draw "
+ "with vertex attrib 0 array enabled, by using bindAttribLocation "
+ "to bind some always-used attribute to location 0.");
+ mAlreadyWarnedAboutFakeVertexAttrib0 = true;
+ }
+
+ gl->fEnableVertexAttribArray(0);
+
+ if (!mFakeVertexAttrib0BufferObject) {
+ gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
+ mFakeVertexAttrib0BufferObjectSize = 0;
+ }
+ gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
+
+ ////
+
+ switch (mGenericVertexAttribTypes[0]) {
+ case LOCAL_GL_FLOAT:
+ gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
+ break;
+
+ case LOCAL_GL_INT:
+ gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
+ break;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+
+ ////
+
+ const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
+ const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
+ if (!checked_dataSize.isValid()) {
+ ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0 array for a draw-operation "
+ "with %d vertices. Try reducing the number of vertices.", vertexCount);
+ return false;
+ }
+ const auto dataSize = checked_dataSize.value();
+
+ if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
+ gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW);
+ mFakeVertexAttrib0BufferObjectSize = dataSize;
+ mFakeVertexAttrib0DataDefined = false;
+ }
+
+ if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::EmulatedUninitializedArray)
+ return true;
+
+ ////
+
+ if (mFakeVertexAttrib0DataDefined &&
+ memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) == 0)
+ {
+ return true;
+ }
+
+ ////
+
+ const UniqueBuffer data(malloc(dataSize));
+ if (!data) {
+ ErrorOutOfMemory("%s: Failed to allocate fake vertex attrib 0 array.",
+ funcName);
+ return false;
+ }
+ auto itr = (uint8_t*)data.get();
+ const auto itrEnd = itr + dataSize;
+ while (itr != itrEnd) {
+ memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
+ itr += bytesPerVert;
+ }
+
+ {
+ gl::GLContext::LocalErrorScope errorScope(*gl);
+
+ gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
+
+ const auto err = errorScope.GetError();
+ if (err) {
+ ErrorOutOfMemory("%s: Failed to upload fake vertex attrib 0 data.", funcName);
+ return false;
+ }
+ }
+
+ ////
+
+ memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
+ mFakeVertexAttrib0DataDefined = true;
+ return true;
+}
+
+void
+WebGLContext::UndoFakeVertexAttrib0()
+{
+ const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
+ if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
+ return;
+
+ if (mBoundVertexArray->mAttribs[0].mBuf) {
+ const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
+ gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.mBuf->mGLName);
+ attrib0.DoVertexAttribPointer(gl, 0);
+ } else {
+ gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
+ }
+
+ gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
+}
+
+static GLuint
+CreateGLTexture(gl::GLContext* gl)
+{
+ MOZ_ASSERT(gl->IsCurrent());
+ GLuint ret = 0;
+ gl->fGenTextures(1, &ret);
+ return ret;
+}
+
+UniquePtr<WebGLContext::FakeBlackTexture>
+WebGLContext::FakeBlackTexture::Create(gl::GLContext* gl, TexTarget target,
+ FakeBlackType type)
+{
+ GLenum texFormat;
+ switch (type) {
+ case FakeBlackType::RGBA0000:
+ texFormat = LOCAL_GL_RGBA;
+ break;
+
+ case FakeBlackType::RGBA0001:
+ texFormat = LOCAL_GL_RGB;
+ break;
+
+ default:
+ MOZ_CRASH("GFX: bad type");
+ }
+
+ UniquePtr<FakeBlackTexture> result(new FakeBlackTexture(gl));
+ gl::ScopedBindTexture scopedBind(gl, result->mGLName, target.get());
+
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
+
+ // We allocate our zeros on the heap, and we overallocate (16 bytes instead of 4) to
+ // minimize the risk of running into a driver bug in texImage2D, as it is a bit
+ // unusual maybe to create 1x1 textures, and the stack may not have the alignment that
+ // TexImage2D expects.
+
+ const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE};
+ UniqueBuffer zeros = moz_xcalloc(1, 16); // Infallible allocation.
+
+ MOZ_ASSERT(gl->IsCurrent());
+
+ if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
+ for (int i = 0; i < 6; ++i) {
+ const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
+ const GLenum error = DoTexImage(gl, curTarget.get(), 0, &dui, 1, 1, 1,
+ zeros.get());
+ if (error) {
+ return nullptr;
+ }
+ }
+ } else {
+ const GLenum error = DoTexImage(gl, target.get(), 0, &dui, 1, 1, 1,
+ zeros.get());
+ if (error) {
+ return nullptr;
+ }
+ }
+
+ return result;
+}
+
+WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl)
+ : mGL(gl)
+ , mGLName(CreateGLTexture(gl))
+{
+}
+
+WebGLContext::FakeBlackTexture::~FakeBlackTexture()
+{
+ mGL->MakeCurrent();
+ mGL->fDeleteTextures(1, &mGLName);
+}
+
+} // namespace mozilla