зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1339256) for build bustage
Backed out changeset a6bb817bae08 (bug 1339256) Backed out changeset 86fe1c44ac5a (bug 1339256) Backed out changeset ed97037dae9c (bug 1339256)
This commit is contained in:
Родитель
0846c32c64
Коммит
7cfff94991
|
@ -8,6 +8,7 @@
|
|||
#include "GLContext.h"
|
||||
#include "mozilla/dom/WebGLRenderingContextBinding.h"
|
||||
#include "WebGLContext.h"
|
||||
#include "WebGLElementArrayCache.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -37,6 +38,9 @@ WebGLBuffer::SetContentAfterBind(GLenum target)
|
|||
switch (target) {
|
||||
case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
|
||||
mContent = Kind::ElementArray;
|
||||
if (!mCache) {
|
||||
mCache.reset(new WebGLElementArrayCache);
|
||||
}
|
||||
break;
|
||||
|
||||
case LOCAL_GL_ARRAY_BUFFER:
|
||||
|
@ -60,8 +64,7 @@ WebGLBuffer::Delete()
|
|||
mContext->MakeContextCurrent();
|
||||
mContext->gl->fDeleteBuffers(1, &mGLName);
|
||||
mByteLength = 0;
|
||||
mIndexCache = nullptr;
|
||||
mIndexRanges.clear();
|
||||
mCache = nullptr;
|
||||
LinkedListElement<WebGLBuffer>::remove(); // remove from mContext->mBuffers
|
||||
}
|
||||
|
||||
|
@ -107,9 +110,14 @@ WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usa
|
|||
if (!ValidateBufferUsageEnum(mContext, funcName, usage))
|
||||
return;
|
||||
|
||||
const auto& gl = mContext->gl;
|
||||
gl->MakeCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, target, this);
|
||||
mContext->InvalidateBufferFetching();
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// bug 790879
|
||||
if (mContext->gl->WorkAroundDriverBugs() &&
|
||||
if (gl->WorkAroundDriverBugs() &&
|
||||
size > INT32_MAX)
|
||||
{
|
||||
mContext->ErrorOutOfMemory("%s: Allocation size too large.", funcName);
|
||||
|
@ -117,31 +125,10 @@ WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usa
|
|||
}
|
||||
#endif
|
||||
|
||||
const void* uploadData = data;
|
||||
|
||||
UniqueBuffer newIndexCache;
|
||||
if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER &&
|
||||
mContext->mNeedsIndexValidation)
|
||||
{
|
||||
newIndexCache = malloc(size);
|
||||
if (!newIndexCache) {
|
||||
mContext->ErrorOutOfMemory("%s: Failed to alloc index cache.", funcName);
|
||||
return;
|
||||
}
|
||||
memcpy(newIndexCache.get(), data, size);
|
||||
uploadData = newIndexCache.get();
|
||||
}
|
||||
|
||||
const auto& gl = mContext->gl;
|
||||
gl->MakeCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, target, this);
|
||||
|
||||
const bool sizeChanges = (size != ByteLength());
|
||||
if (sizeChanges) {
|
||||
mContext->InvalidateBufferFetching();
|
||||
|
||||
gl::GLContext::LocalErrorScope errorScope(*gl);
|
||||
gl->fBufferData(target, size, uploadData, usage);
|
||||
gl->fBufferData(target, size, data, usage);
|
||||
const auto error = errorScope.GetError();
|
||||
|
||||
if (error) {
|
||||
|
@ -150,54 +137,19 @@ WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usa
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
gl->fBufferData(target, size, uploadData, usage);
|
||||
gl->fBufferData(target, size, data, usage);
|
||||
}
|
||||
|
||||
mUsage = usage;
|
||||
mByteLength = size;
|
||||
mIndexCache = Move(newIndexCache);
|
||||
|
||||
if (mIndexCache) {
|
||||
if (mIndexRanges.size()) {
|
||||
mContext->GeneratePerfWarning("[%p] Invalidating %u ranges.", this,
|
||||
uint32_t(mIndexRanges.size()));
|
||||
mIndexRanges.clear();
|
||||
}
|
||||
// Warning: Possibly shared memory. See bug 1225033.
|
||||
if (!ElementArrayCacheBufferData(data, size)) {
|
||||
mByteLength = 0;
|
||||
mContext->ErrorOutOfMemory("%s: Failed update index buffer cache.", funcName);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLBuffer::BufferSubData(GLenum target, size_t dstByteOffset, size_t dataLen,
|
||||
const void* data) const
|
||||
{
|
||||
const char funcName[] = "bufferSubData";
|
||||
|
||||
if (!ValidateRange(funcName, dstByteOffset, dataLen))
|
||||
return;
|
||||
|
||||
if (!CheckedInt<GLintptr>(dataLen).isValid())
|
||||
return mContext->ErrorOutOfMemory("%s: Size too large.", funcName);
|
||||
|
||||
////
|
||||
|
||||
const void* uploadData = data;
|
||||
if (mIndexCache) {
|
||||
const auto cachedDataBegin = (uint8_t*)mIndexCache.get() + dstByteOffset;
|
||||
memcpy(cachedDataBegin, data, dataLen);
|
||||
uploadData = cachedDataBegin;
|
||||
|
||||
InvalidateCacheRange(dstByteOffset, dataLen);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
const auto& gl = mContext->gl;
|
||||
gl->MakeCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, target, this);
|
||||
|
||||
gl->fBufferSubData(target, dstByteOffset, dataLen, uploadData);
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLBuffer::ValidateRange(const char* funcName, size_t byteOffset, size_t byteLen) const
|
||||
{
|
||||
|
@ -219,131 +171,54 @@ WebGLBuffer::ValidateRange(const char* funcName, size_t byteOffset, size_t byteL
|
|||
|
||||
////////////////////////////////////////
|
||||
|
||||
static uint8_t
|
||||
IndexByteSizeByType(GLenum type)
|
||||
bool
|
||||
WebGLBuffer::ElementArrayCacheBufferData(const void* ptr,
|
||||
size_t bufferSizeInBytes)
|
||||
{
|
||||
switch (type) {
|
||||
case LOCAL_GL_UNSIGNED_BYTE: return 1;
|
||||
case LOCAL_GL_UNSIGNED_SHORT: return 2;
|
||||
case LOCAL_GL_UNSIGNED_INT: return 4;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
if (mContext->IsWebGL2())
|
||||
return true;
|
||||
|
||||
if (mContent == Kind::ElementArray)
|
||||
return mCache->BufferData(ptr, bufferSizeInBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
WebGLBuffer::InvalidateCacheRange(size_t byteOffset, size_t byteLength) const
|
||||
WebGLBuffer::ElementArrayCacheBufferSubData(size_t pos, const void* ptr,
|
||||
size_t updateSizeInBytes)
|
||||
{
|
||||
MOZ_ASSERT(mIndexCache);
|
||||
if (mContext->IsWebGL2())
|
||||
return;
|
||||
|
||||
std::vector<IndexRange> invalids;
|
||||
const size_t updateBegin = byteOffset;
|
||||
const size_t updateEnd = updateBegin + byteLength;
|
||||
for (const auto& cur : mIndexRanges) {
|
||||
const auto& range = cur.first;
|
||||
const auto& indexByteSize = IndexByteSizeByType(range.type);
|
||||
const size_t rangeBegin = range.first * indexByteSize;
|
||||
const size_t rangeEnd = rangeBegin + range.count*indexByteSize;
|
||||
if (rangeBegin >= updateEnd || rangeEnd <= updateBegin)
|
||||
continue;
|
||||
invalids.push_back(range);
|
||||
}
|
||||
|
||||
if (invalids.size()) {
|
||||
mContext->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
|
||||
uint32_t(invalids.size()),
|
||||
uint32_t(mIndexRanges.size()));
|
||||
|
||||
for (const auto& cur : invalids) {
|
||||
mIndexRanges.erase(cur);
|
||||
}
|
||||
}
|
||||
if (mContent == Kind::ElementArray)
|
||||
mCache->BufferSubData(pos, ptr, updateSizeInBytes);
|
||||
}
|
||||
|
||||
size_t
|
||||
WebGLBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
||||
{
|
||||
size_t size = mallocSizeOf(this);
|
||||
if (mIndexCache) {
|
||||
size += mByteLength;
|
||||
}
|
||||
return size;
|
||||
size_t sizeOfCache = mCache ? mCache->SizeOfIncludingThis(mallocSizeOf)
|
||||
: 0;
|
||||
return mallocSizeOf(this) + sizeOfCache;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static size_t
|
||||
MaxForRange(const void* data, size_t first, size_t count, const uint32_t ignoredVal)
|
||||
{
|
||||
const T ignoredTVal(ignoredVal);
|
||||
T ret = 0;
|
||||
|
||||
auto itr = (const T*)data + first;
|
||||
const auto end = itr + count;
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
const auto& val = *itr;
|
||||
if (val <= ret)
|
||||
continue;
|
||||
|
||||
if (val == ignoredTVal)
|
||||
continue;
|
||||
|
||||
ret = val;
|
||||
}
|
||||
|
||||
return size_t(ret);
|
||||
}
|
||||
|
||||
const uint32_t kMaxIndexRanges = 256;
|
||||
|
||||
bool
|
||||
WebGLBuffer::ValidateIndexedFetch(GLenum type, uint32_t numFetchable, size_t first,
|
||||
size_t count) const
|
||||
WebGLBuffer::Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count) const
|
||||
{
|
||||
if (!mIndexCache)
|
||||
if (mContext->IsWebGL2())
|
||||
return true;
|
||||
|
||||
if (!count)
|
||||
return true;
|
||||
return mCache->Validate(type, maxAllowed, first, count);
|
||||
}
|
||||
|
||||
const IndexRange range = { type, first, count };
|
||||
auto res = mIndexRanges.insert({ range, size_t(0) });
|
||||
if (mIndexRanges.size() > kMaxIndexRanges) {
|
||||
mContext->GeneratePerfWarning("[%p] Clearing mIndexRanges after exceeding %u.",
|
||||
this, kMaxIndexRanges);
|
||||
mIndexRanges.clear();
|
||||
res = mIndexRanges.insert({ range, size_t(0) });
|
||||
}
|
||||
bool
|
||||
WebGLBuffer::IsElementArrayUsedWithMultipleTypes() const
|
||||
{
|
||||
if (mContext->IsWebGL2())
|
||||
return false;
|
||||
|
||||
const auto& itr = res.first;
|
||||
const auto& didInsert = res.second;
|
||||
|
||||
auto& maxFetchIndex = itr->second;
|
||||
if (didInsert) {
|
||||
const auto& data = mIndexCache.get();
|
||||
const uint32_t ignoreVal = (mContext->IsWebGL2() ? UINT32_MAX : 0);
|
||||
|
||||
switch (type) {
|
||||
case LOCAL_GL_UNSIGNED_BYTE:
|
||||
maxFetchIndex = MaxForRange<uint8_t>(data, first, count, ignoreVal);
|
||||
break;
|
||||
case LOCAL_GL_UNSIGNED_SHORT:
|
||||
maxFetchIndex = MaxForRange<uint16_t>(data, first, count, ignoreVal);
|
||||
break;
|
||||
case LOCAL_GL_UNSIGNED_INT:
|
||||
maxFetchIndex = MaxForRange<uint32_t>(data, first, count, ignoreVal);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
mContext->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %u, %u): %u", this,
|
||||
uint32_t(mIndexRanges.size()), type,
|
||||
uint32_t(first), uint32_t(count),
|
||||
uint32_t(maxFetchIndex));
|
||||
}
|
||||
|
||||
return maxFetchIndex < numFetchable;
|
||||
return mCache->BeenUsedWithMultipleTypes();
|
||||
}
|
||||
|
||||
////
|
||||
|
|
|
@ -6,16 +6,18 @@
|
|||
#ifndef WEBGL_BUFFER_H_
|
||||
#define WEBGL_BUFFER_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "GLDefs.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
#include "WebGLObjectModel.h"
|
||||
#include "WebGLTypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WebGLElementArrayCache;
|
||||
|
||||
class WebGLBuffer final
|
||||
: public nsWrapperCache
|
||||
, public WebGLRefCountedObject<WebGLBuffer>
|
||||
|
@ -44,9 +46,16 @@ public:
|
|||
GLenum Usage() const { return mUsage; }
|
||||
size_t ByteLength() const { return mByteLength; }
|
||||
|
||||
bool ValidateIndexedFetch(GLenum type, uint32_t max_allowed, size_t first, size_t count) const;
|
||||
bool ElementArrayCacheBufferData(const void* ptr, size_t bufferSizeInBytes);
|
||||
|
||||
void ElementArrayCacheBufferSubData(size_t pos, const void* ptr,
|
||||
size_t updateSizeInBytes);
|
||||
|
||||
bool Validate(GLenum type, uint32_t max_allowed, size_t first, size_t count) const;
|
||||
bool ValidateRange(const char* funcName, size_t byteOffset, size_t byteLen) const;
|
||||
|
||||
bool IsElementArrayUsedWithMultipleTypes() const;
|
||||
|
||||
WebGLContext* GetParentObject() const {
|
||||
return mContext;
|
||||
}
|
||||
|
@ -55,8 +64,6 @@ public:
|
|||
|
||||
bool ValidateCanBindToTarget(const char* funcName, GLenum target);
|
||||
void BufferData(GLenum target, size_t size, const void* data, GLenum usage);
|
||||
void BufferSubData(GLenum target, size_t dstByteOffset, size_t dataLen,
|
||||
const void* data) const;
|
||||
|
||||
////
|
||||
|
||||
|
@ -95,32 +102,12 @@ public:
|
|||
protected:
|
||||
~WebGLBuffer();
|
||||
|
||||
void InvalidateCacheRange(size_t offset, size_t length) const;
|
||||
|
||||
Kind mContent;
|
||||
GLenum mUsage;
|
||||
size_t mByteLength;
|
||||
UniquePtr<WebGLElementArrayCache> mCache;
|
||||
size_t mTFBindCount;
|
||||
size_t mNonTFBindCount;
|
||||
|
||||
struct IndexRange final {
|
||||
GLenum type;
|
||||
size_t first;
|
||||
size_t count;
|
||||
|
||||
bool operator<(const IndexRange& x) const {
|
||||
if (type != x.type)
|
||||
return type < x.type;
|
||||
|
||||
if (first != x.first)
|
||||
return first < x.first;
|
||||
|
||||
return count < x.count;
|
||||
}
|
||||
};
|
||||
|
||||
UniqueBuffer mIndexCache;
|
||||
mutable std::map<IndexRange, size_t> mIndexRanges;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -1965,8 +1965,6 @@ protected:
|
|||
bool mNeedsFakeNoStencil;
|
||||
bool mNeedsEmulatedLoneDepthStencil;
|
||||
|
||||
bool mNeedsIndexValidation;
|
||||
|
||||
const bool mAllowFBInvalidation;
|
||||
|
||||
bool Has64BitTimestamps() const;
|
||||
|
@ -2114,6 +2112,50 @@ ValidateTexImageTarget(WebGLContext* webgl, const char* funcName, uint8_t funcDi
|
|||
GLenum rawTexImageTarget, TexImageTarget* const out_texImageTarget,
|
||||
WebGLTexture** const out_tex);
|
||||
|
||||
class UniqueBuffer
|
||||
{
|
||||
// Like UniquePtr<>, but for void* and malloc/calloc/free.
|
||||
void* mBuffer;
|
||||
|
||||
public:
|
||||
UniqueBuffer()
|
||||
: mBuffer(nullptr)
|
||||
{ }
|
||||
|
||||
MOZ_IMPLICIT UniqueBuffer(void* buffer)
|
||||
: mBuffer(buffer)
|
||||
{ }
|
||||
|
||||
~UniqueBuffer() {
|
||||
free(mBuffer);
|
||||
}
|
||||
|
||||
UniqueBuffer(UniqueBuffer&& other) {
|
||||
this->mBuffer = other.mBuffer;
|
||||
other.mBuffer = nullptr;
|
||||
}
|
||||
|
||||
UniqueBuffer& operator =(UniqueBuffer&& other) {
|
||||
free(this->mBuffer);
|
||||
this->mBuffer = other.mBuffer;
|
||||
other.mBuffer = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UniqueBuffer& operator =(void* newBuffer) {
|
||||
free(this->mBuffer);
|
||||
this->mBuffer = newBuffer;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return bool(mBuffer); }
|
||||
|
||||
void* get() const { return mBuffer; }
|
||||
|
||||
UniqueBuffer(const UniqueBuffer& other) = delete; // construct using Move()!
|
||||
void operator =(const UniqueBuffer& other) = delete; // assign using Move()!
|
||||
};
|
||||
|
||||
class ScopedUnpackReset final
|
||||
: public gl::ScopedGLWrapper<ScopedUnpackReset>
|
||||
{
|
||||
|
|
|
@ -402,10 +402,26 @@ WebGLContext::BufferSubDataImpl(GLenum target, WebGLsizeiptr dstByteOffset,
|
|||
if (!buffer)
|
||||
return;
|
||||
|
||||
buffer->BufferSubData(target, size_t(dstByteOffset), dataLen, data);
|
||||
}
|
||||
if (!buffer->ValidateRange(funcName, dstByteOffset, dataLen))
|
||||
return;
|
||||
|
||||
////
|
||||
if (!CheckedInt<GLintptr>(dataLen).isValid()) {
|
||||
ErrorOutOfMemory("%s: Size too large.", funcName);
|
||||
return;
|
||||
}
|
||||
const GLintptr glDataLen(dataLen);
|
||||
|
||||
////
|
||||
|
||||
MakeContextCurrent();
|
||||
const ScopedLazyBind lazyBind(gl, target, buffer);
|
||||
|
||||
// Warning: Possibly shared memory. See bug 1225033.
|
||||
gl->fBufferSubData(target, dstByteOffset, glDataLen, data);
|
||||
|
||||
// Warning: Possibly shared memory. See bug 1225033.
|
||||
buffer->ElementArrayCacheBufferSubData(dstByteOffset, data, size_t(glDataLen));
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset,
|
||||
|
|
|
@ -738,7 +738,7 @@ WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vert
|
|||
return false;
|
||||
|
||||
if (!mMaxFetchedVertices ||
|
||||
!elemArrayBuffer.ValidateIndexedFetch(type, mMaxFetchedVertices, first, vertCount))
|
||||
!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",
|
||||
|
@ -746,6 +746,15 @@ WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vert
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -985,8 +994,8 @@ WebGLContext::ValidateBufferFetching(const char* info)
|
|||
maxInstances = 0;
|
||||
break;
|
||||
}
|
||||
availBytes -= vd.BytesPerVertex(); // Snip off the tail.
|
||||
const size_t vertCapacity = availBytes / vd.ExplicitStride() + 1; // Add +1 for the snipped tail.
|
||||
availBytes -= vd.BytesPerVertex();
|
||||
const size_t vertCapacity = 1 + availBytes / vd.ExplicitStride();
|
||||
|
||||
if (vd.mDivisor == 0) {
|
||||
if (vertCapacity < maxVertices) {
|
||||
|
|
|
@ -725,11 +725,6 @@ WebGLContext::InitAndValidateGL(FailureReason* const out_failReason)
|
|||
|
||||
mFakeVertexAttrib0BufferObject = 0;
|
||||
|
||||
mNeedsIndexValidation = !gl->IsSupported(gl::GLFeature::robust_buffer_access_behavior);
|
||||
if (gfxPrefs::WebGLForceIndexValidation()) {
|
||||
mNeedsIndexValidation = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,622 @@
|
|||
/* -*- 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 "WebGLElementArrayCache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/* WebGLElementArrayCacheTree contains most of the implementation of
|
||||
* WebGLElementArrayCache, which performs WebGL element array buffer validation
|
||||
* for drawElements.
|
||||
*
|
||||
* Attention: Here lie nontrivial data structures, bug-prone algorithms, and
|
||||
* non-canonical tweaks! Whence the explanatory comments, and compiled unit
|
||||
* test.
|
||||
*
|
||||
* *** What problem are we solving here? ***
|
||||
*
|
||||
* WebGL::DrawElements has to validate that the elements are in range wrt the
|
||||
* current vertex attribs. This boils down to the problem, given an array of
|
||||
* integers, of computing the maximum in an arbitrary sub-array. The naive
|
||||
* algorithm has linear complexity; this has been a major performance problem,
|
||||
* see bug 569431. In that bug, we took the approach of caching the max for the
|
||||
* whole array, which does cover most cases (DrawElements typically consumes the
|
||||
* whole element array buffer) but doesn't help in other use cases:
|
||||
* - when doing "partial DrawElements" i.e. consuming only part of the element
|
||||
* array buffer
|
||||
* - when doing frequent "partial buffer updates" i.e. bufferSubData calls
|
||||
* updating parts of the element array buffer
|
||||
*
|
||||
* *** The solution: A binary tree ***
|
||||
*
|
||||
* The solution implemented here is to use a binary tree as the cache data
|
||||
* structure. Each tree node contains the max of its two children nodes. In this
|
||||
* way, finding the maximum in any contiguous sub-array has log complexity
|
||||
* instead of linear complexity.
|
||||
*
|
||||
* Simplistically, if the element array is:
|
||||
*
|
||||
* [1 4 3 2]
|
||||
*
|
||||
* then the corresponding tree is:
|
||||
*
|
||||
* 4
|
||||
* _/ \_
|
||||
* 4 3
|
||||
* / \ / \
|
||||
* 1 4 3 2
|
||||
*
|
||||
* In practice, the bottom-most levels of the tree are both the largest to store
|
||||
* (because they have more nodes), and the least useful performance-wise
|
||||
* (because each node in the bottom levels concerns only few entries in the
|
||||
* elements array buffer, it is cheap to compute).
|
||||
*
|
||||
* For this reason, we stop the tree a few levels above, so that each tree leaf
|
||||
* actually corresponds to more than one element array entry.
|
||||
*
|
||||
* The number of levels that we "drop" is |kSkippedBottomTreeLevels| and the
|
||||
* number of element array entries that each leaf corresponds to, is
|
||||
* |kElementsPerLeaf|. This being a binary tree, we have:
|
||||
*
|
||||
* kElementsPerLeaf = 2 ^ kSkippedBottomTreeLevels.
|
||||
*
|
||||
* *** Storage layout of the binary tree ***
|
||||
*
|
||||
* We take advantage of the specifics of the situation to avoid generalist tree
|
||||
* storage and instead store the tree entries in a vector, mTreeData.
|
||||
*
|
||||
* TreeData is always a vector of length:
|
||||
*
|
||||
* 2 * (number of leaves).
|
||||
*
|
||||
* Its data layout is as follows: mTreeData[0] is unused, mTreeData[1] is the
|
||||
* root node, then at offsets 2..3 is the tree level immediately below the root
|
||||
* node, then at offsets 4..7 is the tree level below that, etc.
|
||||
*
|
||||
* The figure below illustrates this by writing at each tree node the offset
|
||||
* into mTreeData at which it is stored:
|
||||
*
|
||||
* 1
|
||||
* _/ \_
|
||||
* 2 3
|
||||
* / \ / \
|
||||
* 4 5 6 7
|
||||
* ...
|
||||
*
|
||||
* Thus, under the convention that the root level is level 0, we see that level
|
||||
* N is stored at offsets:
|
||||
*
|
||||
* [ 2^n .. 2^(n+1) - 1 ]
|
||||
*
|
||||
* in mTreeData. Likewise, all the usual tree operations have simple
|
||||
* mathematical expressions in terms of mTreeData offsets, see all the methods
|
||||
* such as ParentNode, LeftChildNode, etc.
|
||||
*
|
||||
* *** Design constraint: Element types aren't known at buffer-update time ***
|
||||
*
|
||||
* Note that a key constraint that we're operating under, is that we don't know
|
||||
* the types of the elements by the time WebGL bufferData/bufferSubData methods
|
||||
* are called. The type of elements is only specified in the drawElements call.
|
||||
* This means that we may potentially have to store caches for multiple element
|
||||
* types, for the same element array buffer. Since we don't know yet how many
|
||||
* element types we'll eventually support (extensions add more), the concern
|
||||
* about memory usage is serious. This is addressed by kSkippedBottomTreeLevels
|
||||
* as explained above. Of course, in the typical case where each element array
|
||||
* buffer is only ever used with one type, this is also addressed by having
|
||||
* WebGLElementArrayCache lazily create trees for each type only upon first use.
|
||||
*
|
||||
* Another consequence of this constraint is that when updating the trees, we
|
||||
* have to update all existing trees. So if trees for types uint8_t, uint16_t
|
||||
* and uint32_t have ever been constructed for this buffer, every subsequent
|
||||
* update will have to update all trees even if one of the types is never used
|
||||
* again. That's inefficient, but content should not put indices of different
|
||||
* types in the same element array buffer anyways. Different index types can
|
||||
* only be consumed in separate drawElements calls, so nothing particular is
|
||||
* to be achieved by lumping them in the same buffer object.
|
||||
*/
|
||||
template<typename T>
|
||||
struct WebGLElementArrayCacheTree
|
||||
{
|
||||
/* A too-high kSkippedBottomTreeLevels would harm the performance of small
|
||||
* drawElements calls. A too-low kSkippedBottomTreeLevels would cause undue
|
||||
* memory usage. The current value has been validated by some benchmarking.
|
||||
* See bug 732660.
|
||||
*/
|
||||
static const size_t kSkippedBottomTreeLevels = 3;
|
||||
static const size_t kElementsPerLeaf = 1 << kSkippedBottomTreeLevels;
|
||||
// Since kElementsPerLeaf is POT:
|
||||
static const size_t kElementsPerLeafMask = kElementsPerLeaf - 1;
|
||||
|
||||
private:
|
||||
// The WebGLElementArrayCache that owns this tree:
|
||||
WebGLElementArrayCache& mParent;
|
||||
|
||||
// The tree's internal data storage. Its length is 2 * (number of leaves)
|
||||
// because of its data layout explained in the above class comment.
|
||||
FallibleTArray<T> mTreeData;
|
||||
|
||||
public:
|
||||
// Constructor. Takes a reference to the WebGLElementArrayCache that is to be
|
||||
// the parent. Does not initialize the tree. Should be followed by a call
|
||||
// to Update() to attempt initializing the tree.
|
||||
explicit WebGLElementArrayCacheTree(WebGLElementArrayCache& value)
|
||||
: mParent(value)
|
||||
{
|
||||
}
|
||||
|
||||
T GlobalMaximum() const {
|
||||
return mTreeData[1];
|
||||
}
|
||||
|
||||
// returns the index of the parent node; if treeIndex=1 (the root node),
|
||||
// the return value is 0.
|
||||
static size_t ParentNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return treeIndex >> 1;
|
||||
}
|
||||
|
||||
static bool IsRightNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return treeIndex & 1;
|
||||
}
|
||||
|
||||
static bool IsLeftNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return !IsRightNode(treeIndex);
|
||||
}
|
||||
|
||||
static size_t SiblingNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return treeIndex ^ 1;
|
||||
}
|
||||
|
||||
static size_t LeftChildNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex);
|
||||
return treeIndex << 1;
|
||||
}
|
||||
|
||||
static size_t RightChildNode(size_t treeIndex) {
|
||||
MOZ_ASSERT(treeIndex);
|
||||
return SiblingNode(LeftChildNode(treeIndex));
|
||||
}
|
||||
|
||||
static size_t LeftNeighborNode(size_t treeIndex, size_t distance = 1) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return treeIndex - distance;
|
||||
}
|
||||
|
||||
static size_t RightNeighborNode(size_t treeIndex, size_t distance = 1) {
|
||||
MOZ_ASSERT(treeIndex > 1);
|
||||
return treeIndex + distance;
|
||||
}
|
||||
|
||||
size_t NumLeaves() const {
|
||||
// See class comment for why we the tree storage size is 2 * numLeaves.
|
||||
return mTreeData.Length() >> 1;
|
||||
}
|
||||
|
||||
size_t LeafForElement(size_t element) const {
|
||||
size_t leaf = element / kElementsPerLeaf;
|
||||
MOZ_ASSERT(leaf < NumLeaves());
|
||||
return leaf;
|
||||
}
|
||||
|
||||
size_t LeafForByte(size_t byte) const {
|
||||
return LeafForElement(byte / sizeof(T));
|
||||
}
|
||||
|
||||
// Returns the index, into the tree storage, where a given leaf is stored.
|
||||
size_t TreeIndexForLeaf(size_t leaf) const {
|
||||
// See above class comment. The tree storage is an array of length
|
||||
// 2 * numLeaves. The leaves are stored in its second half.
|
||||
return leaf + NumLeaves();
|
||||
}
|
||||
|
||||
static size_t LastElementUnderSameLeaf(size_t element) {
|
||||
return element | kElementsPerLeafMask;
|
||||
}
|
||||
|
||||
static size_t FirstElementUnderSameLeaf(size_t element) {
|
||||
return element & ~kElementsPerLeafMask;
|
||||
}
|
||||
|
||||
static size_t NextMultipleOfElementsPerLeaf(size_t numElements) {
|
||||
MOZ_ASSERT(numElements >= 1);
|
||||
return ((numElements - 1) | kElementsPerLeafMask) + 1;
|
||||
}
|
||||
|
||||
bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf)
|
||||
{
|
||||
size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf);
|
||||
size_t lastTreeIndex = TreeIndexForLeaf(lastLeaf);
|
||||
|
||||
while (true) {
|
||||
// Given that we tweak these values in nontrivial ways, it doesn't
|
||||
// hurt to do this sanity check.
|
||||
MOZ_ASSERT(firstTreeIndex <= lastTreeIndex);
|
||||
|
||||
// Final case where there is only one node to validate at the
|
||||
// current tree level:
|
||||
if (lastTreeIndex == firstTreeIndex) {
|
||||
const T& curData = mTreeData[firstTreeIndex];
|
||||
return curData <= maxAllowed;
|
||||
}
|
||||
|
||||
// If the first node at current tree level is a right node, handle
|
||||
// it individually and replace it with its right neighbor, which is
|
||||
// a left node.
|
||||
if (IsRightNode(firstTreeIndex)) {
|
||||
const T& curData = mTreeData[firstTreeIndex];
|
||||
if (curData > maxAllowed)
|
||||
return false;
|
||||
|
||||
firstTreeIndex = RightNeighborNode(firstTreeIndex);
|
||||
}
|
||||
|
||||
// If the last node at current tree level is a left node, handle it
|
||||
// individually and replace it with its left neighbor, which is a
|
||||
// right node.
|
||||
if (IsLeftNode(lastTreeIndex)) {
|
||||
const T& curData = mTreeData[lastTreeIndex];
|
||||
if (curData > maxAllowed)
|
||||
return false;
|
||||
|
||||
lastTreeIndex = LeftNeighborNode(lastTreeIndex);
|
||||
}
|
||||
|
||||
/* At this point it can happen that firstTreeIndex and lastTreeIndex
|
||||
* "crossed" eachother. That happens if firstTreeIndex was a right
|
||||
* node and lastTreeIndex was its right neighor: In that case, both
|
||||
* above tweaks happened and as a result, they ended up being
|
||||
* swapped: LastTreeIndex is now the _left_ neighbor of
|
||||
* firstTreeIndex. When that happens, there is nothing left to
|
||||
* validate.
|
||||
*/
|
||||
if (lastTreeIndex == LeftNeighborNode(firstTreeIndex))
|
||||
return true;
|
||||
|
||||
// Walk up one level.
|
||||
firstTreeIndex = ParentNode(firstTreeIndex);
|
||||
lastTreeIndex = ParentNode(lastTreeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the tree from the parent's buffer contents. Fallible, as it
|
||||
// may have to resize the tree storage.
|
||||
bool Update(size_t firstByte, size_t lastByte);
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
||||
{
|
||||
return mallocSizeOf(this) +
|
||||
mTreeData.ShallowSizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
};
|
||||
|
||||
// TreeForType: just a template helper to select the right tree object for a given
|
||||
// element type.
|
||||
template<typename T>
|
||||
struct TreeForType {};
|
||||
|
||||
template<>
|
||||
struct TreeForType<uint8_t>
|
||||
{
|
||||
static UniquePtr<WebGLElementArrayCacheTree<uint8_t>>&
|
||||
Value(WebGLElementArrayCache* b) {
|
||||
return b->mUint8Tree;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TreeForType<uint16_t>
|
||||
{
|
||||
static UniquePtr<WebGLElementArrayCacheTree<uint16_t>>&
|
||||
Value(WebGLElementArrayCache* b) {
|
||||
return b->mUint16Tree;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TreeForType<uint32_t>
|
||||
{
|
||||
static UniquePtr<WebGLElementArrayCacheTree<uint32_t>>&
|
||||
Value(WebGLElementArrayCache* b) {
|
||||
return b->mUint32Tree;
|
||||
}
|
||||
};
|
||||
|
||||
// Calling this method will 1) update the leaves in this interval
|
||||
// from the raw buffer data, and 2) propagate this update up the tree.
|
||||
template<typename T>
|
||||
bool
|
||||
WebGLElementArrayCacheTree<T>::Update(size_t firstByte, size_t lastByte)
|
||||
{
|
||||
MOZ_ASSERT(firstByte <= lastByte);
|
||||
MOZ_ASSERT(lastByte < mParent.mBytes.Length());
|
||||
|
||||
size_t numberOfElements = mParent.mBytes.Length() / sizeof(T);
|
||||
size_t requiredNumLeaves = 0;
|
||||
if (numberOfElements > 0) {
|
||||
/* If we didn't require the number of leaves to be a power of two, then
|
||||
* it would just be equal to
|
||||
*
|
||||
* ceil(numberOfElements / kElementsPerLeaf)
|
||||
*
|
||||
* The way we implement this (division+ceil) operation in integer
|
||||
* arithmetic
|
||||
* is as follows:
|
||||
*/
|
||||
size_t numLeavesNonPOT = (numberOfElements + kElementsPerLeaf - 1) / kElementsPerLeaf;
|
||||
// It only remains to round that up to the next power of two:
|
||||
requiredNumLeaves = RoundUpPow2(numLeavesNonPOT);
|
||||
}
|
||||
|
||||
// Step #0: If needed, resize our tree data storage.
|
||||
if (requiredNumLeaves != NumLeaves()) {
|
||||
// See class comment for why we the tree storage size is 2 * numLeaves.
|
||||
if (!mTreeData.SetLength(2 * requiredNumLeaves, fallible)) {
|
||||
mTreeData.Clear();
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(NumLeaves() == requiredNumLeaves);
|
||||
|
||||
if (NumLeaves()) {
|
||||
// When resizing, update the whole tree, not just the subset
|
||||
// corresponding to the part of the buffer being updated.
|
||||
memset(mTreeData.Elements(), 0, mTreeData.Length() * sizeof(T));
|
||||
firstByte = 0;
|
||||
lastByte = mParent.mBytes.Length() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (NumLeaves() == 0)
|
||||
return true;
|
||||
|
||||
lastByte = std::min(lastByte, NumLeaves() * kElementsPerLeaf * sizeof(T) - 1);
|
||||
if (firstByte > lastByte)
|
||||
return true;
|
||||
|
||||
size_t firstLeaf = LeafForByte(firstByte);
|
||||
size_t lastLeaf = LeafForByte(lastByte);
|
||||
|
||||
MOZ_ASSERT(firstLeaf <= lastLeaf && lastLeaf < NumLeaves());
|
||||
|
||||
size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf);
|
||||
size_t lastTreeIndex = TreeIndexForLeaf(lastLeaf);
|
||||
|
||||
// Step #1: Initialize the tree leaves from plain buffer data.
|
||||
// That is, each tree leaf must be set to the max of the |kElementsPerLeaf|
|
||||
// corresponding buffer entries.
|
||||
|
||||
// Condition-less scope to prevent leaking this scope's variables into the
|
||||
// code below:
|
||||
{
|
||||
// TreeIndex is the index of the tree leaf we're writing, i.e. the
|
||||
// destination index.
|
||||
size_t treeIndex = firstTreeIndex;
|
||||
// srcIndex is the index in the source buffer.
|
||||
size_t srcIndex = firstLeaf * kElementsPerLeaf;
|
||||
while (treeIndex <= lastTreeIndex) {
|
||||
T m = 0;
|
||||
size_t a = srcIndex;
|
||||
size_t srcIndexNextLeaf = std::min(a + kElementsPerLeaf, numberOfElements);
|
||||
for (; srcIndex < srcIndexNextLeaf; srcIndex++) {
|
||||
m = std::max(m, mParent.Element<T>(srcIndex));
|
||||
}
|
||||
mTreeData[treeIndex] = m;
|
||||
treeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Step #2: Propagate the values up the tree. This is simply a matter of
|
||||
// walking up the tree and setting each node to the max of its two children.
|
||||
while (firstTreeIndex > 1) {
|
||||
// Move up one level.
|
||||
firstTreeIndex = ParentNode(firstTreeIndex);
|
||||
lastTreeIndex = ParentNode(lastTreeIndex);
|
||||
|
||||
// Fast-exit case where only one node is updated at the current level.
|
||||
if (firstTreeIndex == lastTreeIndex) {
|
||||
mTreeData[firstTreeIndex] = std::max(mTreeData[LeftChildNode(firstTreeIndex)], mTreeData[RightChildNode(firstTreeIndex)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t child = LeftChildNode(firstTreeIndex);
|
||||
size_t parent = firstTreeIndex;
|
||||
while (parent <= lastTreeIndex) {
|
||||
T a = mTreeData[child];
|
||||
child = RightNeighborNode(child);
|
||||
T b = mTreeData[child];
|
||||
child = RightNeighborNode(child);
|
||||
mTreeData[parent] = std::max(a, b);
|
||||
parent = RightNeighborNode(parent);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WebGLElementArrayCache::WebGLElementArrayCache()
|
||||
{
|
||||
}
|
||||
|
||||
WebGLElementArrayCache::~WebGLElementArrayCache()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLElementArrayCache::BufferData(const void* ptr, size_t byteLength)
|
||||
{
|
||||
if (mBytes.Length() != byteLength) {
|
||||
if (!mBytes.SetLength(byteLength, fallible)) {
|
||||
mBytes.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(mBytes.Length() == byteLength);
|
||||
return BufferSubData(0, ptr, byteLength);
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLElementArrayCache::BufferSubData(size_t pos, const void* ptr,
|
||||
size_t updateByteLength)
|
||||
{
|
||||
MOZ_ASSERT(pos + updateByteLength <= mBytes.Length());
|
||||
if (!updateByteLength)
|
||||
return true;
|
||||
|
||||
// Note, using memcpy on shared racy data is not well-defined, this
|
||||
// will need to use safe-for-races operations when those become available.
|
||||
// See bug 1225033.
|
||||
if (ptr)
|
||||
memcpy(mBytes.Elements() + pos, ptr, updateByteLength);
|
||||
else
|
||||
memset(mBytes.Elements() + pos, 0, updateByteLength);
|
||||
return UpdateTrees(pos, pos + updateByteLength - 1);
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLElementArrayCache::UpdateTrees(size_t firstByte, size_t lastByte)
|
||||
{
|
||||
bool result = true;
|
||||
if (mUint8Tree)
|
||||
result &= mUint8Tree->Update(firstByte, lastByte);
|
||||
if (mUint16Tree)
|
||||
result &= mUint16Tree->Update(firstByte, lastByte);
|
||||
if (mUint32Tree)
|
||||
result &= mUint32Tree->Update(firstByte, lastByte);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool
|
||||
WebGLElementArrayCache::Validate(uint32_t maxAllowed, size_t firstElement,
|
||||
size_t countElements)
|
||||
{
|
||||
// If maxAllowed is >= the max T value, then there is no way that a T index
|
||||
// could be invalid.
|
||||
uint32_t maxTSize = std::numeric_limits<T>::max();
|
||||
if (maxAllowed >= maxTSize)
|
||||
return true;
|
||||
|
||||
T maxAllowedT(maxAllowed);
|
||||
|
||||
// Integer overflow must have been handled earlier, so we assert that
|
||||
// maxAllowedT is exactly the max allowed value.
|
||||
MOZ_ASSERT(uint32_t(maxAllowedT) == maxAllowed);
|
||||
|
||||
if (!mBytes.Length() || !countElements)
|
||||
return true;
|
||||
|
||||
UniquePtr<WebGLElementArrayCacheTree<T>>& tree = TreeForType<T>::Value(this);
|
||||
if (!tree) {
|
||||
tree = MakeUnique<WebGLElementArrayCacheTree<T>>(*this);
|
||||
if (mBytes.Length()) {
|
||||
bool valid = tree->Update(0, mBytes.Length() - 1);
|
||||
if (!valid) {
|
||||
// Do not assert here. This case would happen if an allocation
|
||||
// failed. We've already settled on fallible allocations around
|
||||
// here.
|
||||
tree = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t lastElement = firstElement + countElements - 1;
|
||||
|
||||
// Fast-exit path when the global maximum for the whole element array buffer
|
||||
// falls in the allowed range:
|
||||
T globalMax = tree->GlobalMaximum();
|
||||
if (globalMax <= maxAllowedT)
|
||||
return true;
|
||||
|
||||
const T* elements = Elements<T>();
|
||||
|
||||
// Before calling tree->Validate, we have to validate ourselves the
|
||||
// boundaries of the elements span, to round them to the nearest multiple of
|
||||
// kElementsPerLeaf.
|
||||
size_t firstElementAdjustmentEnd = std::min(lastElement,
|
||||
tree->LastElementUnderSameLeaf(firstElement));
|
||||
while (firstElement <= firstElementAdjustmentEnd) {
|
||||
const T& curData = elements[firstElement];
|
||||
if (curData > maxAllowedT)
|
||||
return false;
|
||||
|
||||
firstElement++;
|
||||
}
|
||||
size_t lastElementAdjustmentEnd = std::max(firstElement,
|
||||
tree->FirstElementUnderSameLeaf(lastElement));
|
||||
while (lastElement >= lastElementAdjustmentEnd) {
|
||||
const T& curData = elements[lastElement];
|
||||
if (curData > maxAllowedT)
|
||||
return false;
|
||||
|
||||
lastElement--;
|
||||
}
|
||||
|
||||
// at this point, for many tiny validations, we're already done.
|
||||
if (firstElement > lastElement)
|
||||
return true;
|
||||
|
||||
// general case
|
||||
return tree->Validate(maxAllowedT, tree->LeafForElement(firstElement),
|
||||
tree->LeafForElement(lastElement));
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed,
|
||||
size_t firstElement, size_t countElements)
|
||||
{
|
||||
if (type == LOCAL_GL_UNSIGNED_BYTE)
|
||||
return Validate<uint8_t>(maxAllowed, firstElement, countElements);
|
||||
if (type == LOCAL_GL_UNSIGNED_SHORT)
|
||||
return Validate<uint16_t>(maxAllowed, firstElement, countElements);
|
||||
if (type == LOCAL_GL_UNSIGNED_INT)
|
||||
return Validate<uint32_t>(maxAllowed, firstElement, countElements);
|
||||
|
||||
MOZ_ASSERT(false, "Invalid type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static size_t
|
||||
SizeOfNullable(mozilla::MallocSizeOf mallocSizeOf, const T& obj)
|
||||
{
|
||||
if (!obj)
|
||||
return 0;
|
||||
return obj->SizeOfIncludingThis(mallocSizeOf);
|
||||
}
|
||||
|
||||
size_t
|
||||
WebGLElementArrayCache::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
||||
{
|
||||
return mallocSizeOf(this) +
|
||||
mBytes.ShallowSizeOfExcludingThis(mallocSizeOf) +
|
||||
SizeOfNullable(mallocSizeOf, mUint8Tree) +
|
||||
SizeOfNullable(mallocSizeOf, mUint16Tree) +
|
||||
SizeOfNullable(mallocSizeOf, mUint32Tree);
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLElementArrayCache::BeenUsedWithMultipleTypes() const
|
||||
{
|
||||
// C++ Standard ($4.7)
|
||||
// "If the source type is bool, the value false is converted to zero and
|
||||
// the value true is converted to one."
|
||||
const int num_types_used = (mUint8Tree != nullptr) +
|
||||
(mUint16Tree != nullptr) +
|
||||
(mUint32Tree != nullptr);
|
||||
return num_types_used > 1;
|
||||
}
|
||||
|
||||
} // end namespace mozilla
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef WEBGL_ELEMENT_ARRAY_CACHE_H
|
||||
#define WEBGL_ELEMENT_ARRAY_CACHE_H
|
||||
|
||||
#include "GLDefs.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nscore.h"
|
||||
#include "nsTArray.h"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template<typename T>
|
||||
struct WebGLElementArrayCacheTree;
|
||||
|
||||
/* WebGLElementArrayCache implements WebGL element array buffer validation for
|
||||
* drawElements.
|
||||
*
|
||||
* Its exposes methods meant to be called by WebGL method implementations:
|
||||
*
|
||||
* - Validate, to be called by WebGLContext::DrawElements, is where we use the
|
||||
* cache.
|
||||
*
|
||||
* - BufferData and BufferSubData, to be called by eponymous WebGL methods, are
|
||||
* how data is fed into the cache.
|
||||
*
|
||||
* Most of the implementation is hidden in the auxilary class template,
|
||||
* WebGLElementArrayCacheTree. Refer to its code for design comments.
|
||||
*/
|
||||
class WebGLElementArrayCache {
|
||||
public:
|
||||
bool BufferData(const void* ptr, size_t byteLength);
|
||||
bool BufferSubData(size_t pos, const void* ptr, size_t updateByteSize);
|
||||
|
||||
bool Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count);
|
||||
|
||||
template<typename T>
|
||||
T Element(size_t i) const { return Elements<T>()[i]; }
|
||||
|
||||
WebGLElementArrayCache();
|
||||
~WebGLElementArrayCache();
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||
bool BeenUsedWithMultipleTypes() const;
|
||||
|
||||
private:
|
||||
/* Returns true if a drawElements call with the given parameters should
|
||||
* succeed, false otherwise.
|
||||
*
|
||||
* In other words, this returns true if all entries in the element array at
|
||||
* positions:
|
||||
*
|
||||
* first .. first+count-1
|
||||
*
|
||||
* are less than or equal to maxAllowed.
|
||||
*
|
||||
* Input parameters:
|
||||
* maxAllowed: Maximum value to be allowed in the specificied portion of
|
||||
* the element array.
|
||||
* first: Start of the portion of the element array to consume.
|
||||
* count: Number of entries from the element array to consume.
|
||||
*
|
||||
* Output parameter:
|
||||
* out_upperBound: Upon success, is set to the actual maximum value in the
|
||||
* specified range, which is then guaranteed to be less
|
||||
* than or equal to maxAllowed. upon failure, is set to
|
||||
* the first value in the specified range, that is greater
|
||||
* than maxAllowed.
|
||||
*/
|
||||
template<typename T>
|
||||
bool Validate(uint32_t maxAllowed, size_t first, size_t count);
|
||||
|
||||
template<typename T>
|
||||
const T* Elements() const {
|
||||
return reinterpret_cast<const T*>(mBytes.Elements());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* Elements() { return reinterpret_cast<T*>(mBytes.Elements()); }
|
||||
|
||||
bool UpdateTrees(size_t firstByte, size_t lastByte);
|
||||
|
||||
template<typename T>
|
||||
friend struct WebGLElementArrayCacheTree;
|
||||
template<typename T>
|
||||
friend struct TreeForType;
|
||||
|
||||
FallibleTArray<uint8_t> mBytes;
|
||||
UniquePtr<WebGLElementArrayCacheTree<uint8_t>> mUint8Tree;
|
||||
UniquePtr<WebGLElementArrayCacheTree<uint16_t>> mUint16Tree;
|
||||
UniquePtr<WebGLElementArrayCacheTree<uint32_t>> mUint32Tree;
|
||||
};
|
||||
|
||||
} // end namespace mozilla
|
||||
|
||||
#endif // WEBGL_ELEMENT_ARRAY_CACHE_H
|
|
@ -173,50 +173,6 @@ enum class WebGLExtensionID : uint8_t {
|
|||
Unknown
|
||||
};
|
||||
|
||||
class UniqueBuffer
|
||||
{
|
||||
// Like UniquePtr<>, but for void* and malloc/calloc/free.
|
||||
void* mBuffer;
|
||||
|
||||
public:
|
||||
UniqueBuffer()
|
||||
: mBuffer(nullptr)
|
||||
{ }
|
||||
|
||||
MOZ_IMPLICIT UniqueBuffer(void* buffer)
|
||||
: mBuffer(buffer)
|
||||
{ }
|
||||
|
||||
~UniqueBuffer() {
|
||||
free(mBuffer);
|
||||
}
|
||||
|
||||
UniqueBuffer(UniqueBuffer&& other) {
|
||||
this->mBuffer = other.mBuffer;
|
||||
other.mBuffer = nullptr;
|
||||
}
|
||||
|
||||
UniqueBuffer& operator =(UniqueBuffer&& other) {
|
||||
free(this->mBuffer);
|
||||
this->mBuffer = other.mBuffer;
|
||||
other.mBuffer = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UniqueBuffer& operator =(void* newBuffer) {
|
||||
free(this->mBuffer);
|
||||
this->mBuffer = newBuffer;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return bool(mBuffer); }
|
||||
|
||||
void* get() const { return mBuffer; }
|
||||
|
||||
UniqueBuffer(const UniqueBuffer& other) = delete; // construct using Move()!
|
||||
void operator =(const UniqueBuffer& other) = delete; // assign using Move()!
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "mozilla/Assertions.h"
|
||||
|
||||
#include "WebGLElementArrayCache.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include "nscore.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
void
|
||||
MakeRandomVector(nsTArray<uint8_t>& a, size_t size)
|
||||
{
|
||||
a.SetLength(size);
|
||||
// only the most-significant bits of rand() are reasonably random.
|
||||
// RAND_MAX can be as low as 0x7fff, and we need 8 bits for the result, so we can only
|
||||
// ignore the 7 least significant bits.
|
||||
for (size_t i = 0; i < size; i++)
|
||||
a[i] = static_cast<uint8_t>((unsigned int)(rand()) >> 7);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
RandomInteger(T a, T b)
|
||||
{
|
||||
T result(a + rand() % (b - a + 1));
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
GLenum
|
||||
GLType()
|
||||
{
|
||||
switch (sizeof(T)) {
|
||||
case 4: return LOCAL_GL_UNSIGNED_INT;
|
||||
case 2: return LOCAL_GL_UNSIGNED_SHORT;
|
||||
case 1: return LOCAL_GL_UNSIGNED_BYTE;
|
||||
default:
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CheckValidate(bool expectSuccess, mozilla::WebGLElementArrayCache& c, GLenum type,
|
||||
uint32_t maxAllowed, size_t first, size_t count)
|
||||
{
|
||||
const bool success = c.Validate(type, maxAllowed, first, count);
|
||||
ASSERT_TRUE(success == expectSuccess);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
CheckValidateOneTypeVariousBounds(mozilla::WebGLElementArrayCache& c, size_t firstByte,
|
||||
size_t countBytes)
|
||||
{
|
||||
size_t first = firstByte / sizeof(T);
|
||||
size_t count = countBytes / sizeof(T);
|
||||
|
||||
GLenum type = GLType<T>();
|
||||
|
||||
T max = 0;
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (c.Element<T>(first + i) > max)
|
||||
max = c.Element<T>(first + i);
|
||||
|
||||
CheckValidate(true, c, type, max, first, count);
|
||||
CheckValidate(true, c, type, T(-1), first, count);
|
||||
if (T(max + 1)) CheckValidate(true, c, type, T(max + 1), first, count);
|
||||
if (max > 0) {
|
||||
CheckValidate(false, c, type, max - 1, first, count);
|
||||
CheckValidate(false, c, type, 0, first, count);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckValidateAllTypes(mozilla::WebGLElementArrayCache& c, size_t firstByte,
|
||||
size_t countBytes)
|
||||
{
|
||||
CheckValidateOneTypeVariousBounds<uint8_t>(c, firstByte, countBytes);
|
||||
CheckValidateOneTypeVariousBounds<uint16_t>(c, firstByte, countBytes);
|
||||
CheckValidateOneTypeVariousBounds<uint32_t>(c, firstByte, countBytes);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
CheckSanity()
|
||||
{
|
||||
const size_t numElems = 64; // should be significantly larger than tree leaf size to
|
||||
// ensure we exercise some nontrivial tree-walking
|
||||
T data[numElems] = {1,0,3,1,2,6,5,4}; // intentionally specify only 8 elements for now
|
||||
size_t numBytes = numElems * sizeof(T);
|
||||
ASSERT_TRUE(numBytes == sizeof(data));
|
||||
|
||||
GLenum type = GLType<T>();
|
||||
|
||||
mozilla::WebGLElementArrayCache c;
|
||||
c.BufferData(data, numBytes);
|
||||
CheckValidate(true, c, type, 6, 0, 8);
|
||||
CheckValidate(false, c, type, 5, 0, 8);
|
||||
CheckValidate(true, c, type, 3, 0, 3);
|
||||
CheckValidate(false, c, type, 2, 0, 3);
|
||||
CheckValidate(true, c, type, 6, 2, 4);
|
||||
CheckValidate(false, c, type, 5, 2, 4);
|
||||
|
||||
c.BufferSubData(5*sizeof(T), data, sizeof(T));
|
||||
CheckValidate(true, c, type, 5, 0, 8);
|
||||
CheckValidate(false, c, type, 4, 0, 8);
|
||||
|
||||
// now test a somewhat larger size to ensure we exceed the size of a tree leaf
|
||||
for(size_t i = 0; i < numElems; i++)
|
||||
data[i] = numElems - i;
|
||||
c.BufferData(data, numBytes);
|
||||
CheckValidate(true, c, type, numElems, 0, numElems);
|
||||
CheckValidate(false, c, type, numElems - 1, 0, numElems);
|
||||
|
||||
ASSERT_TRUE(numElems > 10);
|
||||
CheckValidate(true, c, type, numElems - 10, 10, numElems - 10);
|
||||
CheckValidate(false, c, type, numElems - 11, 10, numElems - 10);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
CheckUintOverflow()
|
||||
{
|
||||
// This test is only for integer types smaller than uint32_t
|
||||
static_assert(sizeof(T) < sizeof(uint32_t), "This test is only for integer types \
|
||||
smaller than uint32_t");
|
||||
|
||||
const size_t numElems = 64; // should be significantly larger than tree leaf size to
|
||||
// ensure we exercise some nontrivial tree-walking
|
||||
T data[numElems];
|
||||
size_t numBytes = numElems * sizeof(T);
|
||||
ASSERT_TRUE(numBytes == sizeof(data));
|
||||
|
||||
GLenum type = GLType<T>();
|
||||
|
||||
mozilla::WebGLElementArrayCache c;
|
||||
|
||||
for(size_t i = 0; i < numElems; i++)
|
||||
data[i] = numElems - i;
|
||||
c.BufferData(data, numBytes);
|
||||
|
||||
// bug 825205
|
||||
uint32_t bigValWrappingToZero = uint32_t(T(-1)) + 1;
|
||||
CheckValidate(true, c, type, bigValWrappingToZero, 0, numElems);
|
||||
CheckValidate(true, c, type, bigValWrappingToZero - 1, 0, numElems);
|
||||
CheckValidate(false, c, type, 0, 0, numElems);
|
||||
}
|
||||
|
||||
TEST(WebGLElementArrayCache, Test)
|
||||
{
|
||||
srand(0); // do not want a random seed here.
|
||||
|
||||
CheckSanity<uint8_t>();
|
||||
CheckSanity<uint16_t>();
|
||||
CheckSanity<uint32_t>();
|
||||
|
||||
CheckUintOverflow<uint8_t>();
|
||||
CheckUintOverflow<uint16_t>();
|
||||
|
||||
nsTArray<uint8_t> v, vsub;
|
||||
mozilla::WebGLElementArrayCache b;
|
||||
|
||||
for (int maxBufferSize = 1; maxBufferSize <= 4096; maxBufferSize *= 2) {
|
||||
// See bug 800612. We originally had | repeat = min(maxBufferSize, 20) |
|
||||
// and a real bug was only caught on Windows and not on Linux due to rand()
|
||||
// producing different values. In that case, the minimum value by which to replace
|
||||
// this 20 to reproduce the bug on Linux, was 25. Replacing it with 64 should give
|
||||
// us some comfort margin.
|
||||
int repeat = std::min(maxBufferSize, 64);
|
||||
for (int i = 0; i < repeat; i++) {
|
||||
size_t size = RandomInteger<size_t>(0, maxBufferSize);
|
||||
MakeRandomVector(v, size);
|
||||
b.BufferData(v.Elements(), size);
|
||||
CheckValidateAllTypes(b, 0, size);
|
||||
|
||||
for (int j = 0; j < 16; j++) {
|
||||
for (int bufferSubDataCalls = 1; bufferSubDataCalls <= 8; bufferSubDataCalls *= 2) {
|
||||
for (int validateCalls = 1; validateCalls <= 8; validateCalls *= 2) {
|
||||
|
||||
size_t offset = 0, subsize = 0;
|
||||
|
||||
for (int k = 0; k < bufferSubDataCalls; k++) {
|
||||
offset = RandomInteger<size_t>(0, size);
|
||||
subsize = RandomInteger<size_t>(0, size - offset);
|
||||
MakeRandomVector(vsub, subsize);
|
||||
b.BufferSubData(offset, vsub.Elements(), subsize);
|
||||
}
|
||||
|
||||
for (int k = 0; k < validateCalls; k++) {
|
||||
offset = RandomInteger<size_t>(0, size);
|
||||
subsize = RandomInteger<size_t>(0, size - offset);
|
||||
CheckValidateAllTypes(b, offset, subsize);
|
||||
}
|
||||
} // validateCalls
|
||||
} // bufferSubDataCalls
|
||||
} // j
|
||||
} // i
|
||||
} // maxBufferSize
|
||||
}
|
||||
|
|
@ -7,8 +7,12 @@
|
|||
with Files('**'):
|
||||
BUG_COMPONENT = ('Core', 'Canvas: 2D')
|
||||
|
||||
with Files('*WebGL*'):
|
||||
BUG_COMPONENT = ('Core', 'Canvas: WebGL')
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestImageBitmapColorUtils.cpp',
|
||||
'TestWebGLElementArrayCache.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
|
|
@ -125,6 +125,7 @@ UNIFIED_SOURCES += [
|
|||
'WebGLContextValidate.cpp',
|
||||
'WebGLContextVertexArray.cpp',
|
||||
'WebGLContextVertices.cpp',
|
||||
'WebGLElementArrayCache.cpp',
|
||||
'WebGLExtensionBase.cpp',
|
||||
'WebGLExtensionBlendMinMax.cpp',
|
||||
'WebGLExtensionColorBufferFloat.cpp',
|
||||
|
|
|
@ -4958,6 +4958,7 @@ skip-if = (os == 'android' || os == 'linux')
|
|||
[generated/test_2_conformance2__rendering__draw-buffers.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance2__rendering__element-index-uint.html]
|
||||
fail-if = (os != 'win')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
|
@ -6124,16 +6125,19 @@ skip-if = (os == 'android' || os == 'linux')
|
|||
[generated/test_2_conformance__buffers__element-array-buffer-delete-recreate.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation-copies-indices.html]
|
||||
fail-if = (os != 'win')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation-crash-with-buffer-sub-data.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation-large-buffer.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation-verifies-too-many-indices.html]
|
||||
fail-if = (os != 'win')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation-with-resized-buffer.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__buffers__index-validation.html]
|
||||
fail-if = (os != 'win')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__canvas__buffer-offscreen-test.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
|
@ -7255,6 +7259,7 @@ skip-if = (os == 'android' || os == 'linux')
|
|||
[generated/test_2_conformance__rendering__draw-arrays-out-of-bounds.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__rendering__draw-elements-out-of-bounds.html]
|
||||
fail-if = (os != 'win')
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
[generated/test_2_conformance__rendering__draw-with-changing-start-vertex-bug.html]
|
||||
skip-if = (os == 'android' || os == 'linux')
|
||||
|
|
|
@ -71,6 +71,20 @@ skip-if = (os == 'android') || (os == 'linux') || (os == 'win')
|
|||
# Timeout on D3D11
|
||||
skip-if = (os == 'win')
|
||||
|
||||
####################
|
||||
# Tests expect conservative index validation, which we skip on WebGL 2.
|
||||
# ANGLE still provides it though, so they pass on windows.
|
||||
[generated/test_2_conformance__rendering__draw-elements-out-of-bounds.html]
|
||||
fail-if = (os != 'win')
|
||||
[generated/test_2_conformance__buffers__index-validation-copies-indices.html]
|
||||
fail-if = (os != 'win')
|
||||
[generated/test_2_conformance__buffers__index-validation.html]
|
||||
fail-if = (os != 'win')
|
||||
[generated/test_2_conformance__buffers__index-validation-verifies-too-many-indices.html]
|
||||
fail-if = (os != 'win')
|
||||
[generated/test_2_conformance2__rendering__element-index-uint.html]
|
||||
fail-if = (os != 'win')
|
||||
|
||||
########################################################################
|
||||
# Complicated
|
||||
|
||||
|
|
|
@ -103,7 +103,6 @@ static const char* const sExtensionNames[] = {
|
|||
"GL_ARB_map_buffer_range",
|
||||
"GL_ARB_occlusion_query2",
|
||||
"GL_ARB_pixel_buffer_object",
|
||||
"GL_ARB_robust_buffer_access_behavior",
|
||||
"GL_ARB_robustness",
|
||||
"GL_ARB_sampler_objects",
|
||||
"GL_ARB_seamless_cube_map",
|
||||
|
@ -158,8 +157,6 @@ static const char* const sExtensionNames[] = {
|
|||
"GL_IMG_texture_compression_pvrtc",
|
||||
"GL_IMG_texture_npot",
|
||||
"GL_KHR_debug",
|
||||
"GL_KHR_robust_buffer_access_behavior",
|
||||
"GL_KHR_robustness",
|
||||
"GL_KHR_texture_compression_astc_hdr",
|
||||
"GL_KHR_texture_compression_astc_ldr",
|
||||
"GL_NV_draw_instanced",
|
||||
|
@ -1009,11 +1006,6 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
|
|||
|
||||
////////////////
|
||||
|
||||
const auto err = mSymbols.fGetError();
|
||||
MOZ_RELEASE_ASSERT(!err);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
LoadMoreSymbols(prefix, trygl);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1151,28 +1143,30 @@ GLContext::LoadMoreSymbols(const char* prefix, bool trygl)
|
|||
};
|
||||
|
||||
if (IsSupported(GLFeature::robustness)) {
|
||||
const auto resetStrategy = GetIntAs<GLuint>(LOCAL_GL_RESET_NOTIFICATION_STRATEGY);
|
||||
if (resetStrategy != LOCAL_GL_LOSE_CONTEXT_ON_RESET) {
|
||||
MOZ_ASSERT(resetStrategy == LOCAL_GL_NO_RESET_NOTIFICATION);
|
||||
NS_WARNING("Robustness supported, but not active!");
|
||||
MarkUnsupported(GLFeature::robustness);
|
||||
}
|
||||
}
|
||||
if (IsSupported(GLFeature::robustness)) {
|
||||
const SymLoadStruct symbols[] = {
|
||||
{ (PRFuncPtr*) &mSymbols.fGetGraphicsResetStatus, { "GetGraphicsResetStatus",
|
||||
"GetGraphicsResetStatusARB",
|
||||
"GetGraphicsResetStatusKHR",
|
||||
"GetGraphicsResetStatusEXT",
|
||||
nullptr } },
|
||||
END_SYMBOLS
|
||||
};
|
||||
if (fnLoadForFeature(symbols, GLFeature::robustness)) {
|
||||
const auto status = mSymbols.fGetGraphicsResetStatus();
|
||||
MOZ_ALWAYS_TRUE(!status);
|
||||
bool hasRobustness = false;
|
||||
|
||||
const auto err = mSymbols.fGetError();
|
||||
MOZ_ALWAYS_TRUE(!err);
|
||||
if (!hasRobustness && IsExtensionSupported(ARB_robustness)) {
|
||||
const SymLoadStruct symbols[] = {
|
||||
{ (PRFuncPtr*) &mSymbols.fGetGraphicsResetStatus, { "GetGraphicsResetStatusARB", nullptr } },
|
||||
END_SYMBOLS
|
||||
};
|
||||
if (fnLoadForExt(symbols, ARB_robustness)) {
|
||||
hasRobustness = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRobustness && IsExtensionSupported(EXT_robustness)) {
|
||||
const SymLoadStruct symbols[] = {
|
||||
{ (PRFuncPtr*) &mSymbols.fGetGraphicsResetStatus, { "GetGraphicsResetStatusEXT", nullptr } },
|
||||
END_SYMBOLS
|
||||
};
|
||||
if (fnLoadForExt(symbols, EXT_robustness)) {
|
||||
hasRobustness = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRobustness) {
|
||||
MarkUnsupported(GLFeature::robustness);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,6 @@ enum class GLFeature {
|
|||
read_buffer,
|
||||
renderbuffer_color_float,
|
||||
renderbuffer_color_half_float,
|
||||
robust_buffer_access_behavior,
|
||||
robustness,
|
||||
sRGB_framebuffer,
|
||||
sRGB_texture,
|
||||
|
@ -200,6 +199,10 @@ class GLContext
|
|||
public:
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(GLContext)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// basic enums
|
||||
public:
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// basic getters
|
||||
public:
|
||||
|
@ -431,7 +434,6 @@ public:
|
|||
ARB_map_buffer_range,
|
||||
ARB_occlusion_query2,
|
||||
ARB_pixel_buffer_object,
|
||||
ARB_robust_buffer_access_behavior,
|
||||
ARB_robustness,
|
||||
ARB_sampler_objects,
|
||||
ARB_seamless_cube_map,
|
||||
|
@ -486,8 +488,6 @@ public:
|
|||
IMG_texture_compression_pvrtc,
|
||||
IMG_texture_npot,
|
||||
KHR_debug,
|
||||
KHR_robust_buffer_access_behavior,
|
||||
KHR_robustness,
|
||||
KHR_texture_compression_astc_hdr,
|
||||
KHR_texture_compression_astc_ldr,
|
||||
NV_draw_instanced,
|
||||
|
@ -571,6 +571,15 @@ private:
|
|||
*/
|
||||
bool IsFeatureProvidedByCoreSymbols(GLFeature feature);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Robustness handling
|
||||
private:
|
||||
/**
|
||||
* The derived class is expected to provide information on whether or not it
|
||||
* supports robustness.
|
||||
*/
|
||||
virtual bool SupportsRobustness() const = 0;
|
||||
|
||||
public:
|
||||
// -----------------------------------------------------------------------------
|
||||
// Error handling
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
|
||||
virtual bool IsDoubleBuffered() const override;
|
||||
|
||||
virtual bool SupportsRobustness() const override { return false; }
|
||||
|
||||
virtual bool SwapBuffers() override;
|
||||
|
||||
virtual void GetWSIInfo(nsCString* const out) const override;
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
|
||||
virtual bool IsDoubleBuffered() const override;
|
||||
|
||||
virtual bool SupportsRobustness() const override { return false; }
|
||||
|
||||
virtual bool SwapBuffers() override;
|
||||
|
||||
virtual void GetWSIInfo(nsCString* const out) const override;
|
||||
|
|
|
@ -58,6 +58,10 @@ public:
|
|||
mIsDoubleBuffered = aIsDB;
|
||||
}
|
||||
|
||||
virtual bool SupportsRobustness() const override {
|
||||
return sEGLLibrary.HasRobustness();
|
||||
}
|
||||
|
||||
virtual bool IsANGLE() const override {
|
||||
return sEGLLibrary.IsANGLE();
|
||||
}
|
||||
|
|
|
@ -532,17 +532,6 @@ static const FeatureInfo sFeatureInfoArr[] = {
|
|||
GLContext::Extensions_End
|
||||
}
|
||||
},
|
||||
{
|
||||
"robust_buffer_access_behavior",
|
||||
GLVersion::NONE,
|
||||
GLESVersion::NONE,
|
||||
GLContext::Extension_None,
|
||||
{
|
||||
GLContext::ARB_robust_buffer_access_behavior,
|
||||
GLContext::KHR_robust_buffer_access_behavior,
|
||||
GLContext::Extensions_End
|
||||
}
|
||||
},
|
||||
{
|
||||
"robustness",
|
||||
GLVersion::NONE,
|
||||
|
@ -551,7 +540,6 @@ static const FeatureInfo sFeatureInfoArr[] = {
|
|||
{
|
||||
GLContext::ARB_robustness,
|
||||
GLContext::EXT_robustness,
|
||||
GLContext::KHR_robustness,
|
||||
GLContext::Extensions_End
|
||||
}
|
||||
},
|
||||
|
|
|
@ -56,6 +56,8 @@ public:
|
|||
|
||||
virtual bool IsDoubleBuffered() const override;
|
||||
|
||||
virtual bool SupportsRobustness() const override;
|
||||
|
||||
virtual bool SwapBuffers() override;
|
||||
|
||||
virtual void GetWSIInfo(nsCString* const out) const override;
|
||||
|
|
|
@ -84,7 +84,7 @@ CreateConfig(EGLConfig* aConfig);
|
|||
#define EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS \
|
||||
LOCAL_EGL_NONE, 0, 0, 0
|
||||
|
||||
static EGLint kTerminationAttribs[] = {
|
||||
static EGLint gTerminationAttribs[] = {
|
||||
EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS
|
||||
};
|
||||
|
||||
|
@ -482,38 +482,31 @@ GLContextEGL::CreateGLContext(CreateContextFlags flags,
|
|||
EGLContext eglShareContext = shareContext ? shareContext->mContext
|
||||
: EGL_NO_CONTEXT;
|
||||
|
||||
std::vector<EGLint> contextAttribs;
|
||||
nsTArray<EGLint> contextAttribs;
|
||||
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_CLIENT_VERSION);
|
||||
contextAttribs.AppendElement(LOCAL_EGL_CONTEXT_CLIENT_VERSION);
|
||||
if (flags & CreateContextFlags::PREFER_ES3)
|
||||
contextAttribs.push_back(3);
|
||||
contextAttribs.AppendElement(3);
|
||||
else
|
||||
contextAttribs.push_back(2);
|
||||
contextAttribs.AppendElement(2);
|
||||
|
||||
if (sEGLLibrary.IsExtensionSupported(GLLibraryEGL::KHR_create_context)) {
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
|
||||
contextAttribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_KHR);
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_FLAGS_KHR);
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR);
|
||||
} else if (sEGLLibrary.IsExtensionSupported(GLLibraryEGL::EXT_create_context_robustness)) {
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
|
||||
contextAttribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_EXT);
|
||||
contextAttribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
|
||||
contextAttribs.push_back(LOCAL_EGL_TRUE);
|
||||
if (sEGLLibrary.HasRobustness()) {
|
||||
// contextAttribs.AppendElement(LOCAL_EGL_CONTEXT_ROBUST_ACCESS_EXT);
|
||||
// contextAttribs.AppendElement(LOCAL_EGL_TRUE);
|
||||
}
|
||||
|
||||
for (const auto& cur : kTerminationAttribs) {
|
||||
contextAttribs.push_back(cur);
|
||||
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(gTerminationAttribs); i++) {
|
||||
contextAttribs.AppendElement(gTerminationAttribs[i]);
|
||||
}
|
||||
|
||||
EGLContext context = sEGLLibrary.fCreateContext(EGL_DISPLAY(),
|
||||
config,
|
||||
eglShareContext,
|
||||
contextAttribs.data());
|
||||
contextAttribs.Elements());
|
||||
if (!context && shareContext) {
|
||||
shareContext = nullptr;
|
||||
context = sEGLLibrary.fCreateContext(EGL_DISPLAY(), config, EGL_NO_CONTEXT,
|
||||
contextAttribs.data());
|
||||
contextAttribs.Elements());
|
||||
}
|
||||
if (!context) {
|
||||
*out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_CREATE");
|
||||
|
@ -554,8 +547,8 @@ TRY_AGAIN_POWER_OF_TWO:
|
|||
pbattrs.AppendElement(bindToTextureFormat);
|
||||
}
|
||||
|
||||
for (const auto& cur : kTerminationAttribs) {
|
||||
pbattrs.AppendElement(cur);
|
||||
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(gTerminationAttribs); i++) {
|
||||
pbattrs.AppendElement(gTerminationAttribs[i]);
|
||||
}
|
||||
|
||||
surface = sEGLLibrary.fCreatePbufferSurface(EGL_DISPLAY(), config, &pbattrs[0]);
|
||||
|
|
|
@ -511,11 +511,9 @@ GLContextGLX::CreateGLContext(CreateContextFlags flags, const SurfaceCaps& caps,
|
|||
if (glx.HasCreateContextAttribs()) {
|
||||
AutoTArray<int, 11> attrib_list;
|
||||
if (glx.HasRobustness()) {
|
||||
const int robust_attribs[] = {
|
||||
LOCAL_GLX_CONTEXT_FLAGS_ARB,
|
||||
LOCAL_GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB,
|
||||
LOCAL_GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB,
|
||||
LOCAL_GLX_LOSE_CONTEXT_ON_RESET_ARB,
|
||||
int robust_attribs[] = {
|
||||
LOCAL_GL_CONTEXT_FLAGS_ARB, LOCAL_GL_CONTEXT_ROBUST_ACCESS_BIT_ARB,
|
||||
LOCAL_GL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, LOCAL_GL_LOSE_CONTEXT_ON_RESET_ARB,
|
||||
};
|
||||
attrib_list.AppendElements(robust_attribs, MOZ_ARRAY_LENGTH(robust_attribs));
|
||||
}
|
||||
|
@ -664,6 +662,12 @@ GLContextGLX::IsDoubleBuffered() const
|
|||
return mDoubleBuffered;
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextGLX::SupportsRobustness() const
|
||||
{
|
||||
return mGLX->HasRobustness();
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextGLX::SwapBuffers()
|
||||
{
|
||||
|
|
|
@ -361,6 +361,12 @@ GLContextWGL::IsDoubleBuffered() const
|
|||
return mIsDoubleBuffered;
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextWGL::SupportsRobustness() const
|
||||
{
|
||||
return sWGLLib.HasRobustness();
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextWGL::SwapBuffers() {
|
||||
if (!mIsDoubleBuffered)
|
||||
|
|
|
@ -53,6 +53,8 @@ public:
|
|||
|
||||
virtual bool IsDoubleBuffered() const override;
|
||||
|
||||
virtual bool SupportsRobustness() const override;
|
||||
|
||||
virtual bool SwapBuffers() override;
|
||||
|
||||
virtual bool SetupLookupFunction() override;
|
||||
|
|
|
@ -75,15 +75,10 @@
|
|||
#define LOCAL_WGL_ACCESS_READ_WRITE 0x0001
|
||||
#define LOCAL_WGL_ACCESS_WRITE_DISCARD 0x0002
|
||||
|
||||
// GL_KHR_robustness
|
||||
#define LOCAL_GL_CONTEXT_ROBUST_ACCESS 0x90F3
|
||||
|
||||
// Others
|
||||
#define LOCAL_EGL_PRESERVED_RESOURCES 0x3030
|
||||
#define LOCAL_EGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_EXT 0x3138
|
||||
#define LOCAL_GL_RESET_NOTIFICATION_STRATEGY 0x8256
|
||||
#define LOCAL_GL_LOSE_CONTEXT_ON_RESET 0x8252
|
||||
#define LOCAL_GL_NO_RESET_NOTIFICATION 0x8261
|
||||
#define LOCAL_GL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
|
||||
#define LOCAL_GL_CONTEXT_LOST 0x9242
|
||||
#define LOCAL_GL_CONTEXT_FLAGS_ARB 0x2094
|
||||
#define LOCAL_GL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
||||
|
|
|
@ -53,7 +53,6 @@ static const char* sEGLExtensionNames[] = {
|
|||
"EGL_ANGLE_platform_angle",
|
||||
"EGL_ANGLE_platform_angle_d3d",
|
||||
"EGL_ANGLE_d3d_share_handle_client_buffer"
|
||||
"EGL_KHR_create_context",
|
||||
};
|
||||
|
||||
#if defined(ANDROID)
|
||||
|
|
|
@ -105,7 +105,6 @@ public:
|
|||
ANGLE_platform_angle,
|
||||
ANGLE_platform_angle_d3d,
|
||||
ANGLE_d3d_share_handle_client_buffer,
|
||||
KHR_create_context,
|
||||
Extensions_Max
|
||||
};
|
||||
|
||||
|
@ -328,6 +327,10 @@ public:
|
|||
return IsExtensionSupported(ANGLE_surface_d3d_texture_2d_share_handle);
|
||||
}
|
||||
|
||||
bool HasRobustness() const {
|
||||
return IsExtensionSupported(EXT_create_context_robustness);
|
||||
}
|
||||
|
||||
bool ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface);
|
||||
|
||||
bool EnsureInitialized(bool forceAccel, nsACString* const out_failureId);
|
||||
|
|
|
@ -628,7 +628,6 @@ private:
|
|||
DECL_GFX_PREF(Live, "webgl.enable-webgl2", WebGL2Enabled, bool, true);
|
||||
DECL_GFX_PREF(Live, "webgl.force-enabled", WebGLForceEnabled, bool, false);
|
||||
DECL_GFX_PREF(Once, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.force-index-validation", WebGLForceIndexValidation, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false);
|
||||
DECL_GFX_PREF(Live, "webgl.max-warnings-per-context", WebGLMaxWarningsPerContext, uint32_t, 32);
|
||||
DECL_GFX_PREF(Live, "webgl.min_capability_mode", WebGLMinCapabilityMode, bool, false);
|
||||
|
|
|
@ -4540,7 +4540,6 @@ pref("webgl.msaa-force", false);
|
|||
pref("webgl.prefer-16bpp", false);
|
||||
pref("webgl.default-no-alpha", false);
|
||||
pref("webgl.force-layers-readback", false);
|
||||
pref("webgl.force-index-validation", false);
|
||||
pref("webgl.lose-context-on-memory-pressure", false);
|
||||
pref("webgl.can-lose-context-in-foreground", true);
|
||||
pref("webgl.restore-context-when-visible", true);
|
||||
|
|
Загрузка…
Ссылка в новой задаче