Files
palemoon27/dom/canvas/WebGLContextDraw.cpp
T
roytam1 9d253b796d import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1235610 - Add xpctall support for Bitrig and DragonFly. r=glandium (02b7d40eb3)
- Bug 1252072 - Prevent ASan instrumentation for unsafe xpcom functions. r=froydnj (881bfc18c0)
- Bug 1245099 - Fixed uninitialized variable warning. r=bsmedberg (6481aad0d5)
- Bug 1196370 - Remove the clang assembly workaround from bug 1028613. r=ehsan (b8b6f1a5e8)
- Bug 1234860 - move win32 NS_InvokeByIndex implementation to a separate assembly file; r=aklotz,ted.m (73ca54348f)
- Bug 1248534 (part 1) - Remove XPT encoding support. r=khuey. (e84c4dfd32)
- Bug 1248534 (part 2) - Remove unused XPT flags. r=khuey. (264be694d0)
- Bug 1248534 (part 3) - Remove almost all support for XPT annotations. r=khuey. (97a0d86e0b)
- Bug 1248534 (part 4) - Remove unused fields from XPTConstValue. r=khuey. (9eccf9eba5)
- Bug 1248534 (part 5) - Remove XPTDatapool. r=khuey. (139dab7a9f)
- Bug 1248534 (part 6) - Stack-allocate XPTState. r=khuey. (f32ce7cc9e)
- Bug 1249174 (part 7) - Only define XPTArena::name if XPT_ARENA_LOGGING is defined. r=khuey. (174a20c0a3)
- Bug 1248534 (part 8) - Remove useless XPT freeing code. r=khuey. (a5ade9739b)
- Bug 1248534 (part 9) - Remove XPT arena logging code. r=khuey. (a996dfad76)
- Bug 1251458 - Reinstate annotation handling in .xpt files. r=khuey. (fbe8d573bd)
- Bug 1249174 (part 1) - Don't store the unused XPTTypeDescriptorTags::argnum2 field in memory. r=khuey. (e1d12965eb)
- Bug 1253877 - Baldr: cast -1 to uint8 to avoid 'shifting negative' error on CLOSED TREE (r=bustage) (cdf8031264)
- Bug 1258599 - OdinMonkey: MIPS: Only reserving stack for argument registers on O32. r=huangwenjun06 (a77220a9fa)
- Bug 1253137 - Baldr: fold if_else into if to match ml-proto (r=sunfish) (54e89aafe3)
- Bug 1249174 (part 2) - Shrink xptiInterfaceEntry by reordering its fields. r=khuey. (c4d7c15e6e)
- Bug 1253137 - Baldr: update br_table syntax to match ml-proto (r=sunfish) (e7f253e4d8)
- Bug 1249174 (part 3) - Don't store the unused XPTInterfaceDirectoryEntry::name_space field in memory. r=khuey. (c308508e74)
- Bug 1249174 (part 4) - Don't store unused XPTHeader fields in memory. r=khuey. (2dfd238a54)
- Bug 1249174 (part 5) - Remove the useless BLK_HDR::size field. r=khuey. (1a362c278e)
- Bug 1249174 (part 6) - Shrink XPTTypeDescriptor. r=khuey. (5a313327fe)
- Bug 1249174 (part 7.5) - Avoid wasted space around XPT strings. r=khuey. (ac3653802c)
- Bug 1249174 (part 8) - Shrink XPTInterfaceDescriptor. r=khuey. (e9dc929d37)
- Bug 1254188 - Baldr: handle recycled phis when closing a loop with a  value (r=bbouvier) (2cb0895472)
- Bug 1254167: Don't allow folding to full range for atomic accesses; r=sunfish (96a851efda)
- Bug 1256637: Set definition before returning early in EmitBrTable; r=luke (0fdb365e82)
- Bug 1255695 - BaldrMonkey: Implement unaligned accesses. r=luke (205b798249)
- Bug 1238121 - Properly guard Profiler's RAII classes r=BenWa f=mystor (a3db8e6bc9)
- Bug 1251787 - Remove remaining references to MOZILLA_XPCOMRT_API from tools. r=mstange (1632205437)
- align profiler (c2638ecf1f)
- Bug 1235502 - Fix -Wunreachable-code warning in tools/profiler/. r=BenWa (0d564937c9)
- Bug 1221846 - Properly close the tasktracer property in the GeckoSampler JSON blob. r=BenWa (dca640fb03)
- Bug 1239498 - Use Stackwalk64 on win x64. r=jrmuizel (50ffe8a649)
- Bug 1258269: Declare logging string-literals in exception_handler.cc as 'const char[]' to fix build warning & for consistency. r=ted (a9454e735f)
- parts of Bug 1151175 - Update libvpx update.py for 1.4.0. (8700fa48ab)
- Bug 1249590 - Bullet-proofing AsyncShutdown wrt exceptions;r=froydnj (7e512f1029)
- Bug 1021151 - avoid memory leak in NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT, use nsAutoPtr instead of naked ptr. r=Ehsan (d0eb754af2)
- Bug 1255223 - Null crash when logging weak maps. r=mccr8. (0d02b4b469)
- Bug 1250134 - assert mJSRuntime when IsGrayJS() is true. r=mccr8 (a3987102ed)
- Bug 1254131: Fix non-unified wasm build. r=luke (cacb86e4d0)
- Bug 1246929 - Skip installing functions and properties on builtins for the self-hosting global. r=Waldo (aa04041de9)
- Bug 986294 - Remove Proxy.create from addon-sdk. r=mossop (da3d6c40ea)
- Bug 1243805 - Replace Proxy.create with new Proxy in devtools l10n code. r=jryans (9886f857be)
- Bug 1245141 - Use new Proxy for AddonManager.addonTypes. r=mossop (6cc8dd0870)
- Bug 1253866 - Remove Proxy.create from crash tests. r=bz (76a421cf97)
- Bug 892903 - Remove Proxy.create and Proxy.createFunction. r=efaust (7b572deb10)
- Bug 1049041 - Remove scary warning about mutating [[Prototype]]. r=efaust. (367ac3f6c5)
- Bug 1257445 - #ifdef on __GLIBC__ for sched_getcpu, which is a glibc feature. r=jimb (171a1729c1)
- Followup for bug 1257445 - Remove the AutoStopwatch::getCPU implementation using sched_getcpu. r=jimb (221f52d4c4)
- Bug 1178317 - eliminate large static constructor from ShimInterfaceInfo.cpp; r=poiru (aa66704aee)
- Bug 1247580 P1 Allow old nsIX509Cert serialized objects to be read off disk. r=bz (1f8bc280a6)
- Bug 1247580 P2 Add gtest to ensure we can continue to deserialize old security info strings. r=bz (323059ac29)
- Bug 864842 - Show error for browsing Windows drive without media, r=michal (1e438bdf2f)
- Bug 1233283 - Remove unless tmp from ReadDir in nsLocalFileWin. r=froydnj (1565d1cc14)
- Bug 1258498: Use fallible allocation in nsScriptableInputStream::ReadBytes. r= froydnj (eebcb8050d)
- Bug 1236108: Add temp directory for sandboxed content processes to directory r=bsmedberg (8384b33c10)
- Bug 1255362 - Null-check GetContainer() before using it in image-related ConfigureLayer() methods. r=mstange a=Tomcat (beab7149f1)
- Bug 1205473 - Add a state bit to optimize building event regions. r=mattwoodrow (c8b1eb9839)
- Bug 1222886 - Remove unused nsCSSParser::{SetStyleSheet,SetChildLoader,SetQuirkMode} methods. r=bzbarsky (c9c6621083)
- Bug 1247327. Fix WebGL acceptance rates in telemetry. r=milan (de10319664)
- Bug 1249664 - Save dropped-down state in nsPresState. r=dbaron (734c3ee18b)
- Bug 1256745 - Cancel the DidPaint timer in SetShell(nullptr). r=mattwoodrow (d2a4202512)
- Bug 1238846 (part 1) - Remove some dead code in nsLayoutUtils. r=mattwoodrow. (e6af806e52)
- Bug 1252414 - Handle lost_context for webgl ClearBuffer*. r=jgilbert (e6728605ff)
- Bug 1228687: ScopedResolveTexturesForDraw needs the context to be current, so make those calls earlier. r=jgilbert (6e731187c5)
- Bug 1247532 - Annotate intentional switch fallthrough to suppress -Wimplicit-fallthrough warning in dom/canvas/. r=jgilbert (d7faec1848)
- Bug 1249483 - Stop filling A with 1.0 on readback from no-alpha. - r=jrmuizel (9bf71fb220)
2024-03-10 21:39:46 +08:00

867 lines
28 KiB
C++

/* -*- 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 "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"
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();
};
ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
const char* funcName,
bool* const out_error)
: mWebGL(webgl)
{
MOZ_ASSERT(webgl->gl->IsCurrent());
typedef decltype(WebGLContext::mBound2DTextures) TexturesT;
const auto fnResolveAll = [this, funcName](const TexturesT& textures)
{
const auto len = textures.Length();
for (uint32_t texUnit = 0; texUnit < len; ++texUnit) {
WebGLTexture* tex = textures[texUnit];
if (!tex)
continue;
FakeBlackType fakeBlack;
if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack))
return false;
if (fakeBlack == FakeBlackType::None)
continue;
mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack);
mRebindRequests.push_back({texUnit, tex});
}
return true;
};
bool ok = true;
ok &= fnResolveAll(mWebGL->mBound2DTextures);
ok &= fnResolveAll(mWebGL->mBoundCubeMapTextures);
ok &= fnResolveAll(mWebGL->mBound3DTextures);
ok &= fnResolveAll(mWebGL->mBound2DArrayTextures);
if (!ok) {
mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.", funcName);
}
*out_error = !ok;
}
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);
}
void
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("fnGetSlot failed.");
}
UniquePtr<FakeBlackTexture>& fakeBlackTex = *slot;
if (!fakeBlackTex) {
fakeBlackTex.reset(new FakeBlackTexture(gl, target, fakeBlack));
}
gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
}
////////////////////////////////////////
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(GLint first, GLsizei count, GLsizei primcount,
const char* info)
{
if (first < 0 || count < 0) {
ErrorInvalidValue("%s: negative first or count", info);
return false;
}
if (primcount < 0) {
ErrorInvalidValue("%s: negative primcount", info);
return false;
}
if (!ValidateStencilParamsForDrawCall()) {
return false;
}
// If count is 0, there's nothing to do.
if (count == 0 || primcount == 0) {
return false;
}
// Any checks below this depend on a program being available.
if (!mCurrentProgram) {
ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info);
return false;
}
if (!ValidateBufferFetching(info)) {
return false;
}
CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count;
if (!checked_firstPlusCount.isValid()) {
ErrorInvalidOperation("%s: overflow in first+count", info);
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", info);
return false;
}
if (uint32_t(primcount) > mMaxFetchedInstances) {
ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
return false;
}
MOZ_ASSERT(gl->IsCurrent());
if (mBoundDrawFramebuffer) {
if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
return false;
} else {
ClearBackbufferIfNeeded();
}
if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) {
return false;
}
return true;
}
void
WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei count)
{
const char funcName[] = "drawArrays";
if (IsContextLost())
return;
if (!ValidateDrawModeEnum(mode, funcName))
return;
MakeContextCurrent();
bool error;
ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
if (error)
return;
if (!DrawArrays_check(first, count, 1, funcName))
return;
RunContextLossTimer();
{
ScopedMaskWorkaround autoMask(*this);
gl->fDrawArrays(mode, first, count);
}
Draw_cleanup(funcName);
}
void
WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
{
const char funcName[] = "drawArraysInstanced";
if (IsContextLost())
return;
if (!ValidateDrawModeEnum(mode, funcName))
return;
MakeContextCurrent();
bool error;
ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
if (error)
return;
if (!DrawArrays_check(first, count, primcount, funcName))
return;
if (!DrawInstanced_check(funcName))
return;
RunContextLossTimer();
{
ScopedMaskWorkaround autoMask(*this);
gl->fDrawArraysInstanced(mode, first, count, primcount);
}
Draw_cleanup(funcName);
}
bool
WebGLContext::DrawElements_check(GLsizei count, GLenum type,
WebGLintptr byteOffset, GLsizei primcount,
const char* info, GLuint* out_upperBound)
{
if (count < 0 || byteOffset < 0) {
ErrorInvalidValue("%s: negative count or offset", info);
return false;
}
if (primcount < 0) {
ErrorInvalidValue("%s: negative primcount", info);
return false;
}
if (!ValidateStencilParamsForDrawCall()) {
return false;
}
// If count is 0, there's nothing to do.
if (count == 0 || primcount == 0)
return false;
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", info, type);
return false;
}
if (byteOffset % bytesPerElem != 0) {
ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
info);
return false;
}
const GLsizei first = byteOffset / bytesPerElem;
const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(count);
if (!checked_byteCount.isValid()) {
ErrorInvalidValue("%s: overflow in byteCount", info);
return false;
}
// Any checks below this depend on a program being available.
if (!mCurrentProgram) {
ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info);
return false;
}
if (!mBoundVertexArray->mElementArrayBuffer) {
ErrorInvalidOperation("%s: must have element array buffer binding", info);
return false;
}
WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
if (!elemArrayBuffer.ByteLength()) {
ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info);
return false;
}
CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
if (!checked_neededByteCount.isValid()) {
ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info);
return false;
}
if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) {
ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info);
return false;
}
if (!ValidateBufferFetching(info))
return false;
if (!mMaxFetchedVertices ||
!elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, count, out_upperBound))
{
ErrorInvalidOperation(
"%s: bound vertex attribute buffers do not have sufficient "
"size for given indices from the bound element array", info);
return false;
}
if (uint32_t(primcount) > mMaxFetchedInstances) {
ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
return false;
}
// Bug 1008310 - Check if buffer has been used with a different previous type
if (elemArrayBuffer.IsElementArrayUsedWithMultipleTypes()) {
GenerateWarning("%s: bound element array buffer previously used with a type other than "
"%s, this will affect performance.",
info,
WebGLContext::EnumName(type));
}
MOZ_ASSERT(gl->IsCurrent());
if (mBoundDrawFramebuffer) {
if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
return false;
} else {
ClearBackbufferIfNeeded();
}
if (!DoFakeVertexAttrib0(mMaxFetchedVertices)) {
return false;
}
return true;
}
void
WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type,
WebGLintptr byteOffset)
{
const char funcName[] = "drawElements";
if (IsContextLost())
return;
if (!ValidateDrawModeEnum(mode, funcName))
return;
MakeContextCurrent();
bool error;
ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
if (error)
return;
GLuint upperBound = 0;
if (!DrawElements_check(count, type, byteOffset, 1, funcName, &upperBound))
return;
RunContextLossTimer();
{
ScopedMaskWorkaround autoMask(*this);
if (gl->IsSupported(gl::GLFeature::draw_range_elements)) {
gl->fDrawRangeElements(mode, 0, upperBound, count, type,
reinterpret_cast<GLvoid*>(byteOffset));
} else {
gl->fDrawElements(mode, count, type,
reinterpret_cast<GLvoid*>(byteOffset));
}
}
Draw_cleanup(funcName);
}
void
WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type,
WebGLintptr byteOffset, GLsizei primcount)
{
const char funcName[] = "drawElementsInstanced";
if (IsContextLost())
return;
if (!ValidateDrawModeEnum(mode, funcName))
return;
MakeContextCurrent();
bool error;
ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
if (error)
return;
GLuint upperBound = 0;
if (!DrawElements_check(count, type, byteOffset, primcount, funcName, &upperBound))
return;
if (!DrawInstanced_check(funcName))
return;
RunContextLossTimer();
{
ScopedMaskWorkaround autoMask(*this);
gl->fDrawElementsInstanced(mode, count, type,
reinterpret_cast<GLvoid*>(byteOffset),
primcount);
}
Draw_cleanup(funcName);
}
void WebGLContext::Draw_cleanup(const char* funcName)
{
UndoFakeVertexAttrib0();
if (!mBoundDrawFramebuffer) {
Invalidate();
mShouldPresent = true;
MOZ_ASSERT(!mBackbufferNeedsClear);
}
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& fba = mBoundDrawFramebuffer->ColorAttachment(0);
if (fba.IsDefined()) {
fba.Size(&destWidth, &destHeight);
}
} 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;
uint32_t attribs = mBoundVertexArray->mAttribs.Length();
for (uint32_t i = 0; i < attribs; ++i) {
const WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[i];
// If the attrib array isn't enabled, there's nothing to check;
// it's a static value.
if (!vd.enabled)
continue;
if (vd.buf == nullptr) {
ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %d!", info, i);
return false;
}
// If the attrib is not in use, then we don't have to validate
// it, just need to make sure that the binding is non-null.
if (!mActiveProgramLinkInfo->HasActiveAttrib(i))
continue;
// the base offset
CheckedUint32 checked_byteLength = CheckedUint32(vd.buf->ByteLength()) - vd.byteOffset;
CheckedUint32 checked_sizeOfLastElement = CheckedUint32(vd.componentSize()) * vd.size;
if (!checked_byteLength.isValid() ||
!checked_sizeOfLastElement.isValid())
{
ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i);
return false;
}
if (checked_byteLength.value() < checked_sizeOfLastElement.value()) {
maxVertices = 0;
maxInstances = 0;
break;
}
CheckedUint32 checked_maxAllowedCount = ((checked_byteLength - checked_sizeOfLastElement) / vd.actualStride()) + 1;
if (!checked_maxAllowedCount.isValid()) {
ErrorInvalidOperation("%s: integer overflow occured while checking vertex attrib %d", info, i);
return false;
}
if (vd.divisor == 0) {
maxVertices = std::min(maxVertices, checked_maxAllowedCount.value());
hasPerVertex = true;
} else {
CheckedUint32 checked_curMaxInstances = checked_maxAllowedCount * vd.divisor;
uint32_t curMaxInstances = UINT32_MAX;
// If this isn't valid, it's because we overflowed our
// uint32 above. Just leave this as UINT32_MAX, since
// sizeof(uint32) becomes our limiting factor.
if (checked_curMaxInstances.isValid()) {
curMaxInstances = checked_curMaxInstances.value();
}
maxInstances = std::min(maxInstances, curMaxInstances);
}
}
mBufferFetchingIsVerified = true;
mBufferFetchingHasPerVertex = hasPerVertex;
mMaxFetchedVertices = maxVertices;
mMaxFetchedInstances = maxInstances;
return true;
}
WebGLVertexAttrib0Status
WebGLContext::WhatDoesVertexAttrib0Need()
{
MOZ_ASSERT(mCurrentProgram);
MOZ_ASSERT(mActiveProgramLinkInfo);
// work around Mac OSX crash, see bug 631420
#ifdef XP_MACOSX
if (gl->WorkAroundDriverBugs() &&
mBoundVertexArray->IsAttribArrayEnabled(0) &&
!mActiveProgramLinkInfo->HasActiveAttrib(0))
{
return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
}
#endif
if (MOZ_LIKELY(gl->IsGLES() ||
mBoundVertexArray->IsAttribArrayEnabled(0)))
{
return WebGLVertexAttrib0Status::Default;
}
return mActiveProgramLinkInfo->HasActiveAttrib(0)
? WebGLVertexAttrib0Status::EmulatedInitializedArray
: WebGLVertexAttrib0Status::EmulatedUninitializedArray;
}
bool
WebGLContext::DoFakeVertexAttrib0(GLuint vertexCount)
{
WebGLVertexAttrib0Status 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;
}
CheckedUint32 checked_dataSize = CheckedUint32(vertexCount) * 4 * sizeof(GLfloat);
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;
}
GLuint dataSize = checked_dataSize.value();
if (!mFakeVertexAttrib0BufferObject) {
gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
}
// if the VBO status is already exactly what we need, or if the only difference is that it's initialized and
// we don't need it to be, then consider it OK
bool vertexAttrib0BufferStatusOK =
mFakeVertexAttrib0BufferStatus == whatDoesAttrib0Need ||
(mFakeVertexAttrib0BufferStatus == WebGLVertexAttrib0Status::EmulatedInitializedArray &&
whatDoesAttrib0Need == WebGLVertexAttrib0Status::EmulatedUninitializedArray);
if (!vertexAttrib0BufferStatusOK ||
mFakeVertexAttrib0BufferObjectSize < dataSize ||
mFakeVertexAttrib0BufferObjectVector[0] != mVertexAttrib0Vector[0] ||
mFakeVertexAttrib0BufferObjectVector[1] != mVertexAttrib0Vector[1] ||
mFakeVertexAttrib0BufferObjectVector[2] != mVertexAttrib0Vector[2] ||
mFakeVertexAttrib0BufferObjectVector[3] != mVertexAttrib0Vector[3])
{
mFakeVertexAttrib0BufferStatus = whatDoesAttrib0Need;
mFakeVertexAttrib0BufferObjectSize = dataSize;
mFakeVertexAttrib0BufferObjectVector[0] = mVertexAttrib0Vector[0];
mFakeVertexAttrib0BufferObjectVector[1] = mVertexAttrib0Vector[1];
mFakeVertexAttrib0BufferObjectVector[2] = mVertexAttrib0Vector[2];
mFakeVertexAttrib0BufferObjectVector[3] = mVertexAttrib0Vector[3];
gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
GetAndFlushUnderlyingGLErrors();
if (mFakeVertexAttrib0BufferStatus == WebGLVertexAttrib0Status::EmulatedInitializedArray) {
auto array = MakeUniqueFallible<GLfloat[]>(4 * vertexCount);
if (!array) {
ErrorOutOfMemory("Fake attrib0 array.");
return false;
}
for(size_t i = 0; i < vertexCount; ++i) {
array[4 * i + 0] = mVertexAttrib0Vector[0];
array[4 * i + 1] = mVertexAttrib0Vector[1];
array[4 * i + 2] = mVertexAttrib0Vector[2];
array[4 * i + 3] = mVertexAttrib0Vector[3];
}
gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, array.get(), LOCAL_GL_DYNAMIC_DRAW);
} else {
gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW);
}
GLenum error = GetAndFlushUnderlyingGLErrors();
gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
// note that we do this error checking and early return AFTER having restored the buffer binding above
if (error) {
ErrorOutOfMemory("Ran out of memory 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;
}
}
gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
return true;
}
void
WebGLContext::UndoFakeVertexAttrib0()
{
WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
return;
if (mBoundVertexArray->HasAttrib(0) && mBoundVertexArray->mAttribs[0].buf) {
const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.buf->mGLName);
if (attrib0.integer) {
gl->fVertexAttribIPointer(0,
attrib0.size,
attrib0.type,
attrib0.stride,
reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
} else {
gl->fVertexAttribPointer(0,
attrib0.size,
attrib0.type,
attrib0.normalized,
attrib0.stride,
reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
}
} 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;
}
WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl, TexTarget target,
FakeBlackType type)
: mGL(gl)
, mGLName(CreateGLTexture(gl))
{
GLenum texFormat;
switch (type) {
case FakeBlackType::RGBA0000:
texFormat = LOCAL_GL_RGBA;
break;
case FakeBlackType::RGBA0001:
texFormat = LOCAL_GL_RGB;
break;
default:
MOZ_CRASH("bad type");
}
gl::ScopedBindTexture scopedBind(mGL, mGLName, target.get());
mGL->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
mGL->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.
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(mGL, curTarget.get(), 0, &dui, 1, 1, 1,
zeros.get());
if (error)
MOZ_CRASH("Unexpected error during FakeBlack creation.");
}
} else {
const GLenum error = DoTexImage(mGL, target.get(), 0, &dui, 1, 1, 1,
zeros.get());
if (error)
MOZ_CRASH("Unexpected error during FakeBlack creation.");
}
}
WebGLContext::FakeBlackTexture::~FakeBlackTexture()
{
mGL->MakeCurrent();
mGL->fDeleteTextures(1, &mGLName);
}
} // namespace mozilla