/* -*- 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 "GeckoProfiler.h" #include "MozFramebuffer.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 "WebGLTransformFeedback.h" #include "WebGLVertexArray.h" #include "WebGLVertexAttribData.h" #include 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 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& 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()); const std::vector* attachList = nullptr; const auto& fb = mWebGL->mBoundDrawFramebuffer; if (fb) { attachList = &(fb->ResolvedCompleteData()->texDrawBuffers); } 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.empty()) 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* { 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* slot = fnGetSlot(); if (!slot) { MOZ_CRASH("GFX: fnGetSlot failed."); } UniquePtr& 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::ValidateStencilParamsForDrawCall(const char* const funcName) const { const auto stencilBits = [&]() -> uint8_t { if (!mStencilTestEnabled) return 0; if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0; if (mBoundDrawFramebuffer->StencilAttachment().IsDefined()) return 8; if (mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined()) return 8; return 0; }(); const uint32_t stencilMax = (1 << stencilBits) - 1; const auto fnMask = [&](const uint32_t x) { return x & stencilMax; }; const auto fnClamp = [&](const int32_t x) { return std::max(0, std::min(x, (int32_t)stencilMax)); }; bool ok = true; ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack)); ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack)); ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack)); if (!ok) { ErrorInvalidOperation("%s: Stencil front/back state must effectively match." " (before front/back comparison, WRITEMASK and VALUE_MASK" " are masked with (2^s)-1, and REF is clamped to" " [0, (2^s)-1], where `s` is the number of enabled stencil" " bits in the draw framebuffer)", funcName); } return ok; } //////////////////////////////////////// template static bool DoSetsIntersect(const std::set& a, const std::set& b) { std::vector 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* const webgl, const char* const funcName, const GLenum mode, const Maybe& lastRequiredVertex, const uint32_t instanceCount, bool* const out_error) : mWebGL(webgl) , mDidFake(false) { MOZ_ASSERT(mWebGL->gl->IsCurrent()); if (!mWebGL->BindCurFBForDraw(funcName)) { *out_error = true; return; } if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) { *out_error = true; return; } if (!mWebGL->ValidateStencilParamsForDrawCall(funcName)) { *out_error = true; return; } if (!mWebGL->mActiveProgramLinkInfo) { mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName); *out_error = true; return; } const auto& linkInfo = mWebGL->mActiveProgramLinkInfo; //// // Check UBO sizes. 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; } // Technically we don't know that this will be updated yet, but we can // speculatively mark it. buffer->ResetLastUpdateFenceId(); } } //// const auto& fetchLimits = linkInfo->GetDrawFetchLimits(funcName); if (!fetchLimits) { *out_error = true; return; } if (lastRequiredVertex && instanceCount) { if (lastRequiredVertex.value() >= fetchLimits->maxVerts) { mWebGL->ErrorInvalidOperation("%s: Vertex fetch requires vertex #%u, but" " attribs only supply %" PRIu64 ".", funcName, lastRequiredVertex.value(), fetchLimits->maxVerts); *out_error = true; return; } if (instanceCount > fetchLimits->maxInstances) { mWebGL->ErrorInvalidOperation("%s: Instance fetch requires %u, but" " attribs only supply %" PRIu64 ".", funcName, instanceCount, fetchLimits->maxInstances); *out_error = true; return; } } //// if (lastRequiredVertex) { if (!mWebGL->DoFakeVertexAttrib0(funcName, lastRequiredVertex.value())) { *out_error = true; return; } mDidFake = true; } //// 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(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; } }; static bool HasInstancedDrawing(const WebGLContext& webgl) { return webgl.IsWebGL2() || webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays); } //////////////////////////////////////// bool WebGLContext::DrawArrays_check(const char* const funcName, const GLint first, const GLsizei vertCount, const GLsizei instanceCount, Maybe* const out_lastVert) { if (!ValidateNonNegative(funcName, "first", first) || !ValidateNonNegative(funcName, "vertCount", vertCount) || !ValidateNonNegative(funcName, "instanceCount", instanceCount)) { 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) { *out_lastVert = Nothing(); } else { const auto lastVert_checked = CheckedInt(first) + vertCount - 1; if (!lastVert_checked.isValid()) { ErrorOutOfMemory("%s: `first+vertCount` out of range.", funcName); return false; } *out_lastVert = Some(lastVert_checked.value()); } return true; } void WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount, GLsizei instanceCount, const char* const funcName) { AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS); if (IsContextLost()) return; const gl::GLContext::TlsScope inTls(gl); Maybe lastVert; if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert)) return; bool error = false; const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount, &error); if (error) return; const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount, instanceCount, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); if (vertCount && instanceCount) { AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS); if (HasInstancedDrawing(*this)) { gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount); } else { MOZ_ASSERT(instanceCount == 1); gl->fDrawArrays(mode, first, vertCount); } } } Draw_cleanup(funcName); scopedTF.Advance(); } //////////////////////////////////////// bool WebGLContext::DrawElements_check(const char* const funcName, const GLsizei rawIndexCount, const GLenum type, const WebGLintptr byteOffset, const GLsizei instanceCount, Maybe* const out_lastVert) { if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive && !mBoundTransformFeedback->mIsPaused) { ErrorInvalidOperation("%s: DrawElements* functions are incompatible with" " transform feedback.", funcName); return false; } if (!ValidateNonNegative(funcName, "vertCount", rawIndexCount) || !ValidateNonNegative(funcName, "byteOffset", byteOffset) || !ValidateNonNegative(funcName, "instanceCount", instanceCount)) { return false; } const auto indexCount = uint32_t(rawIndexCount); uint8_t bytesPerIndex = 0; switch (type) { case LOCAL_GL_UNSIGNED_BYTE: bytesPerIndex = 1; break; case LOCAL_GL_UNSIGNED_SHORT: bytesPerIndex = 2; break; case LOCAL_GL_UNSIGNED_INT: if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) { bytesPerIndex = 4; } break; } if (!bytesPerIndex) { ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type); return false; } if (byteOffset % bytesPerIndex != 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 != bytesPerIndex) { mPrimRestartTypeBytes = bytesPerIndex; const uint32_t ones = UINT32_MAX >> (32 - 8*mPrimRestartTypeBytes); gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART); gl->fPrimitiveRestartIndex(ones); } } //// // Index fetching if (!indexCount || !instanceCount) { *out_lastVert = Nothing(); return true; } const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer; size_t availBytes = 0; if (indexBuffer) { MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible."); availBytes = indexBuffer->ByteLength(); } const auto availIndices = AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex); if (indexCount > availIndices) { ErrorInvalidOperation("%s: Index buffer too small.", funcName); return false; } *out_lastVert = indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount); 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::DrawElementsInstanced(GLenum mode, GLsizei indexCount, GLenum type, WebGLintptr byteOffset, GLsizei instanceCount, const char* const funcName) { AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS); if (IsContextLost()) return; const gl::GLContext::TlsScope inTls(gl); Maybe lastVert; if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount, &lastVert)) { return; } bool error = false; const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount, &error); if (error) return; const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); { UniquePtr errorScope; if (gl->IsANGLE()) { errorScope.reset(new gl::GLContext::LocalErrorScope(*gl)); } if (indexCount && instanceCount) { AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS); if (HasInstancedDrawing(*this)) { gl->fDrawElementsInstanced(mode, indexCount, type, reinterpret_cast(byteOffset), instanceCount); } else { MOZ_ASSERT(instanceCount == 1); gl->fDrawElements(mode, indexCount, type, reinterpret_cast(byteOffset)); } } 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 = mDefaultFB->mSize.width; destHeight = mDefaultFB->mSize.height; } 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; } } } WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const { MOZ_ASSERT(mCurrentProgram); MOZ_ASSERT(mActiveProgramLinkInfo); 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 (!mActiveProgramLinkInfo->attrib0Active) { // Ensure that the legacy code has enough buffer. return WebGLVertexAttrib0Status::EmulatedUninitializedArray; } const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled; return isAttribArray0Enabled ? WebGLVertexAttrib0Status::Default : WebGLVertexAttrib0Status::EmulatedInitializedArray; } bool WebGLContext::DoFakeVertexAttrib0(const char* const funcName, const uint32_t lastVert) { 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(lastVert)+1) * bytesPerVert; if (!checked_dataSize.isValid()) { ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0" " array for a draw-operation with %" PRIu64 " vertices. Try" " reducing the number of vertices.", uint64_t(lastVert) + 1); 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::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 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->fDeleteTextures(1, &mGLName); } } // namespace mozilla