michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebGLElementArrayCache.h" michael@0: michael@0: #include "nsTArray.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: michael@0: static void michael@0: SetUpperBound(uint32_t* out_upperBound, uint32_t newBound) michael@0: { michael@0: if (!out_upperBound) michael@0: return; michael@0: michael@0: *out_upperBound = newBound; michael@0: } michael@0: michael@0: static void michael@0: UpdateUpperBound(uint32_t* out_upperBound, uint32_t newBound) michael@0: { michael@0: if (!out_upperBound) michael@0: return; michael@0: michael@0: *out_upperBound = std::max(*out_upperBound, newBound); michael@0: } michael@0: michael@0: /* michael@0: * WebGLElementArrayCacheTree contains most of the implementation of WebGLElementArrayCache, michael@0: * which performs WebGL element array buffer validation for drawElements. michael@0: * michael@0: * Attention: Here lie nontrivial data structures, bug-prone algorithms, and non-canonical tweaks! michael@0: * Whence the explanatory comments, and compiled unit test. michael@0: * michael@0: * *** What problem are we solving here? *** michael@0: * michael@0: * WebGL::DrawElements has to validate that the elements are in range wrt the current vertex attribs. michael@0: * This boils down to the problem, given an array of integers, of computing the maximum in an arbitrary michael@0: * sub-array. The naive algorithm has linear complexity; this has been a major performance problem, michael@0: * see bug 569431. In that bug, we took the approach of caching the max for the whole array, which michael@0: * does cover most cases (DrawElements typically consumes the whole element array buffer) but doesn't michael@0: * help in other use cases: michael@0: * - when doing "partial DrawElements" i.e. consuming only part of the element array buffer michael@0: * - when doing frequent "partial buffer updates" i.e. bufferSubData calls updating parts of the michael@0: * element array buffer michael@0: * michael@0: * *** The solution: a binary tree *** michael@0: * michael@0: * The solution implemented here is to use a binary tree as the cache data structure. Each tree node michael@0: * contains the max of its two children nodes. In this way, finding the maximum in any contiguous sub-array michael@0: * has log complexity instead of linear complexity. michael@0: * michael@0: * Simplistically, if the element array is michael@0: * michael@0: * 1 4 3 2 michael@0: * michael@0: * then the corresponding tree is michael@0: * michael@0: * 4 michael@0: * _/ \_ michael@0: * 4 3 michael@0: * / \ / \ michael@0: * 1 4 3 2 michael@0: * michael@0: * In practice, the bottom-most levels of the tree are both the largest to store (because they michael@0: * have more nodes), and the least useful performance-wise (because each node in the bottom michael@0: * levels concerns only few entries in the elements array buffer, it is cheap to compute). michael@0: * michael@0: * For this reason, we stop the tree a few levels above, so that each tree leaf actually corresponds michael@0: * to more than one element array entry. michael@0: * michael@0: * The number of levels that we "drop" is |sSkippedBottomTreeLevels| and the number of element array entries michael@0: * that each leaf corresponds to, is |sElementsPerLeaf|. This being a binary tree, we have michael@0: * michael@0: * sElementsPerLeaf = 2 ^ sSkippedBottomTreeLevels. michael@0: * michael@0: * *** Storage layout of the binary tree *** michael@0: * michael@0: * We take advantage of the specifics of the situation to avoid generalist tree storage and instead michael@0: * store the tree entries in a vector, mTreeData. michael@0: * michael@0: * The number of leaves is given by mNumLeaves, and mTreeData is always a vector of length michael@0: * michael@0: * 2 * mNumLeaves. michael@0: * michael@0: * Its data layout is as follows: mTreeData[0] is unused, mTreeData[1] is the root node, michael@0: * then at offsets 2..3 is the tree level immediately below the root node, then at offsets 4..7 michael@0: * is the tree level below that, etc. michael@0: * michael@0: * The figure below illustrates this by writing at each tree node the offset into mTreeData at michael@0: * which it is stored: michael@0: * michael@0: * 1 michael@0: * _/ \_ michael@0: * 2 3 michael@0: * / \ / \ michael@0: * 4 5 6 7 michael@0: * ... michael@0: * michael@0: * Thus, under the convention that the root level is level 0, we see that level N is stored at offsets michael@0: * michael@0: * [ 2^n .. 2^(n+1) - 1 ] michael@0: * michael@0: * in mTreeData. Likewise, all the usual tree operations have simple mathematical expressions in michael@0: * terms of mTreeData offsets, see all the methods such as ParentNode, LeftChildNode, etc. michael@0: * michael@0: * *** Design constraint: element types aren't known at buffer-update time *** michael@0: * michael@0: * Note that a key constraint that we're operating under, is that we don't know the types of the elements michael@0: * by the time WebGL bufferData/bufferSubData methods are called. The type of elements is only michael@0: * specified in the drawElements call. This means that we may potentially have to store caches for michael@0: * multiple element types, for the same element array buffer. Since we don't know yet how many michael@0: * element types we'll eventually support (extensions add more), the concern about memory usage is serious. michael@0: * This is addressed by sSkippedBottomTreeLevels as explained above. Of course, in the typical michael@0: * case where each element array buffer is only ever used with one type, this is also addressed michael@0: * by having WebGLElementArrayCache lazily create trees for each type only upon first use. michael@0: * michael@0: * Another consequence of this constraint is that when invalidating the trees, we have to invalidate michael@0: * all existing trees. So if trees for types uint8_t, uint16_t and uint32_t have ever been constructed for this buffer, michael@0: * every subsequent invalidation will have to invalidate all trees even if one of the types is never michael@0: * used again. This implies that it is important to minimize the cost of invalidation i.e. michael@0: * do lazy updates upon use as opposed to immediately updating invalidated trees. This poses a problem: michael@0: * it is nontrivial to keep track of the part of the tree that's invalidated. The current solution michael@0: * can only keep track of an invalidated interval, from |mFirstInvalidatedLeaf| to |mLastInvalidatedLeaf|. michael@0: * The problem is that if one does two small, far-apart partial buffer updates, the resulting invalidated michael@0: * area is very large even though only a small part of the array really needed to be invalidated. michael@0: * The real solution to this problem would be to use a smarter data structure to keep track of the michael@0: * invalidated area, probably an interval tree. Meanwhile, we can probably live with the current situation michael@0: * as the unfavorable case seems to be a small corner case: in order to run into performance issues, michael@0: * the number of bufferSubData in between two consecutive draws must be small but greater than 1, and michael@0: * the partial buffer updates must be small and far apart. Anything else than this corner case michael@0: * should run fast in the current setting. michael@0: */ michael@0: template michael@0: struct WebGLElementArrayCacheTree michael@0: { michael@0: // A too-high sSkippedBottomTreeLevels would harm the performance of small drawElements calls michael@0: // A too-low sSkippedBottomTreeLevels would cause undue memory usage. michael@0: // The current value has been validated by some benchmarking. See bug 732660. michael@0: static const size_t sSkippedBottomTreeLevels = 3; michael@0: static const size_t sElementsPerLeaf = 1 << sSkippedBottomTreeLevels; michael@0: static const size_t sElementsPerLeafMask = sElementsPerLeaf - 1; // sElementsPerLeaf is POT michael@0: michael@0: private: michael@0: WebGLElementArrayCache& mParent; michael@0: FallibleTArray mTreeData; michael@0: size_t mNumLeaves; michael@0: bool mInvalidated; michael@0: size_t mFirstInvalidatedLeaf; michael@0: size_t mLastInvalidatedLeaf; michael@0: michael@0: public: michael@0: WebGLElementArrayCacheTree(WebGLElementArrayCache& p) michael@0: : mParent(p) michael@0: , mNumLeaves(0) michael@0: , mInvalidated(false) michael@0: , mFirstInvalidatedLeaf(0) michael@0: , mLastInvalidatedLeaf(0) michael@0: { michael@0: ResizeToParentSize(); michael@0: } michael@0: michael@0: T GlobalMaximum() const { michael@0: MOZ_ASSERT(!mInvalidated); michael@0: return mTreeData[1]; michael@0: } michael@0: michael@0: // returns the index of the parent node; if treeIndex=1 (the root node), michael@0: // the return value is 0. michael@0: static size_t ParentNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return treeIndex >> 1; michael@0: } michael@0: michael@0: static bool IsRightNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return treeIndex & 1; michael@0: } michael@0: michael@0: static bool IsLeftNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return !IsRightNode(treeIndex); michael@0: } michael@0: michael@0: static size_t SiblingNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return treeIndex ^ 1; michael@0: } michael@0: michael@0: static size_t LeftChildNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex); michael@0: return treeIndex << 1; michael@0: } michael@0: michael@0: static size_t RightChildNode(size_t treeIndex) { michael@0: MOZ_ASSERT(treeIndex); michael@0: return SiblingNode(LeftChildNode(treeIndex)); michael@0: } michael@0: michael@0: static size_t LeftNeighborNode(size_t treeIndex, size_t distance = 1) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return treeIndex - distance; michael@0: } michael@0: michael@0: static size_t RightNeighborNode(size_t treeIndex, size_t distance = 1) { michael@0: MOZ_ASSERT(treeIndex > 1); michael@0: return treeIndex + distance; michael@0: } michael@0: michael@0: size_t LeafForElement(size_t element) { michael@0: size_t leaf = element / sElementsPerLeaf; michael@0: MOZ_ASSERT(leaf < mNumLeaves); michael@0: return leaf; michael@0: } michael@0: michael@0: size_t LeafForByte(size_t byte) { michael@0: return LeafForElement(byte / sizeof(T)); michael@0: } michael@0: michael@0: // Returns the index, into the tree storage, where a given leaf is stored michael@0: size_t TreeIndexForLeaf(size_t leaf) { michael@0: // See above class comment. The tree storage is an array of length 2*mNumLeaves. michael@0: // The leaves are stored in its second half. michael@0: return leaf + mNumLeaves; michael@0: } michael@0: michael@0: static size_t LastElementUnderSameLeaf(size_t element) { michael@0: return element | sElementsPerLeafMask; michael@0: } michael@0: michael@0: static size_t FirstElementUnderSameLeaf(size_t element) { michael@0: return element & ~sElementsPerLeafMask; michael@0: } michael@0: michael@0: static size_t NextMultipleOfElementsPerLeaf(size_t numElements) { michael@0: return ((numElements - 1) | sElementsPerLeafMask) + 1; michael@0: } michael@0: michael@0: bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf, michael@0: uint32_t* out_upperBound) michael@0: { michael@0: MOZ_ASSERT(!mInvalidated); michael@0: michael@0: size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf); michael@0: size_t lastTreeIndex = TreeIndexForLeaf(lastLeaf); michael@0: michael@0: while (true) { michael@0: // given that we tweak these values in nontrivial ways, it doesn't hurt to do michael@0: // this sanity check michael@0: MOZ_ASSERT(firstTreeIndex <= lastTreeIndex); michael@0: michael@0: // final case where there is only 1 node to validate at the current tree level michael@0: if (lastTreeIndex == firstTreeIndex) { michael@0: const T& curData = mTreeData[firstTreeIndex]; michael@0: UpdateUpperBound(out_upperBound, curData); michael@0: return curData <= maxAllowed; michael@0: } michael@0: michael@0: // if the first node at current tree level is a right node, handle it individually michael@0: // and replace it with its right neighbor, which is a left node michael@0: if (IsRightNode(firstTreeIndex)) { michael@0: const T& curData = mTreeData[firstTreeIndex]; michael@0: UpdateUpperBound(out_upperBound, curData); michael@0: if (curData > maxAllowed) michael@0: return false; michael@0: firstTreeIndex = RightNeighborNode(firstTreeIndex); michael@0: } michael@0: michael@0: // if the last node at current tree level is a left node, handle it individually michael@0: // and replace it with its left neighbor, which is a right node michael@0: if (IsLeftNode(lastTreeIndex)) { michael@0: const T& curData = mTreeData[lastTreeIndex]; michael@0: UpdateUpperBound(out_upperBound, curData); michael@0: if (curData > maxAllowed) michael@0: return false; michael@0: lastTreeIndex = LeftNeighborNode(lastTreeIndex); michael@0: } michael@0: michael@0: // at this point it can happen that firstTreeIndex and lastTreeIndex "crossed" each michael@0: // other. That happens if firstTreeIndex was a right node and lastTreeIndex was its michael@0: // right neighor: in that case, both above tweaks happened and as a result, they ended michael@0: // up being swapped: lastTreeIndex is now the _left_ neighbor of firstTreeIndex. michael@0: // When that happens, there is nothing left to validate. michael@0: if (lastTreeIndex == LeftNeighborNode(firstTreeIndex)) { michael@0: return true; michael@0: } michael@0: michael@0: // walk up 1 level michael@0: firstTreeIndex = ParentNode(firstTreeIndex); michael@0: lastTreeIndex = ParentNode(lastTreeIndex); michael@0: } michael@0: } michael@0: michael@0: template michael@0: static U NextPowerOfTwo(U x) { michael@0: U result = 1; michael@0: while (result < x) michael@0: result <<= 1; michael@0: MOZ_ASSERT(result >= x); michael@0: MOZ_ASSERT((result & (result - 1)) == 0); michael@0: return result; michael@0: } michael@0: michael@0: bool ResizeToParentSize() michael@0: { michael@0: size_t numberOfElements = mParent.ByteSize() / sizeof(T); michael@0: size_t requiredNumLeaves = (numberOfElements + sElementsPerLeaf - 1) / sElementsPerLeaf; michael@0: michael@0: size_t oldNumLeaves = mNumLeaves; michael@0: mNumLeaves = NextPowerOfTwo(requiredNumLeaves); michael@0: Invalidate(0, mParent.ByteSize() - 1); michael@0: michael@0: // see class comment for why we the tree storage size is 2 * mNumLeaves michael@0: if (!mTreeData.SetLength(2 * mNumLeaves)) { michael@0: return false; michael@0: } michael@0: if (mNumLeaves != oldNumLeaves) { michael@0: memset(mTreeData.Elements(), 0, mTreeData.Length() * sizeof(mTreeData[0])); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void Invalidate(size_t firstByte, size_t lastByte); michael@0: michael@0: void Update(); michael@0: michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + mTreeData.SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: }; michael@0: michael@0: // TreeForType: just a template helper to select the right tree object for a given michael@0: // element type. michael@0: template michael@0: struct TreeForType {}; michael@0: michael@0: template<> michael@0: struct TreeForType michael@0: { michael@0: static WebGLElementArrayCacheTree*& Run(WebGLElementArrayCache *b) { return b->mUint8Tree; } michael@0: }; michael@0: michael@0: template<> michael@0: struct TreeForType michael@0: { michael@0: static WebGLElementArrayCacheTree*& Run(WebGLElementArrayCache *b) { return b->mUint16Tree; } michael@0: }; michael@0: michael@0: template<> michael@0: struct TreeForType michael@0: { michael@0: static WebGLElementArrayCacheTree*& Run(WebGLElementArrayCache *b) { return b->mUint32Tree; } michael@0: }; michael@0: michael@0: // When the buffer gets updated from firstByte to lastByte, michael@0: // calling this method will notify the tree accordingly michael@0: template michael@0: void WebGLElementArrayCacheTree::Invalidate(size_t firstByte, size_t lastByte) michael@0: { michael@0: lastByte = std::min(lastByte, mNumLeaves * sElementsPerLeaf * sizeof(T) - 1); michael@0: if (firstByte > lastByte) { michael@0: return; michael@0: } michael@0: michael@0: size_t firstLeaf = LeafForByte(firstByte); michael@0: size_t lastLeaf = LeafForByte(lastByte); michael@0: michael@0: if (mInvalidated) { michael@0: mFirstInvalidatedLeaf = std::min(firstLeaf, mFirstInvalidatedLeaf); michael@0: mLastInvalidatedLeaf = std::max(lastLeaf, mLastInvalidatedLeaf); michael@0: } else { michael@0: mInvalidated = true; michael@0: mFirstInvalidatedLeaf = firstLeaf; michael@0: mLastInvalidatedLeaf = lastLeaf; michael@0: } michael@0: } michael@0: michael@0: michael@0: // When tree has been partially invalidated, from mFirstInvalidatedLeaf to michael@0: // mLastInvalidatedLeaf, calling this method will 1) update the leaves in this interval michael@0: // from the raw buffer data, and 2) propagate this update up the tree michael@0: template michael@0: void WebGLElementArrayCacheTree::Update() michael@0: { michael@0: if (!mInvalidated) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(mLastInvalidatedLeaf < mNumLeaves); michael@0: michael@0: size_t firstTreeIndex = TreeIndexForLeaf(mFirstInvalidatedLeaf); michael@0: size_t lastTreeIndex = TreeIndexForLeaf(mLastInvalidatedLeaf); michael@0: michael@0: // Step #1: initialize the tree leaves from plain buffer data. michael@0: // That is, each tree leaf must be set to the max of the |sElementsPerLeaf| corresponding michael@0: // buffer entries. michael@0: // condition-less scope to prevent leaking this scope's variables into the code below michael@0: { michael@0: // treeIndex is the index of the tree leaf we're writing, i.e. the destination index michael@0: size_t treeIndex = firstTreeIndex; michael@0: // srcIndex is the index in the source buffer michael@0: size_t srcIndex = mFirstInvalidatedLeaf * sElementsPerLeaf; michael@0: size_t numberOfElements = mParent.ByteSize() / sizeof(T); michael@0: while (treeIndex <= lastTreeIndex) { michael@0: T m = 0; michael@0: size_t a = srcIndex; michael@0: size_t srcIndexNextLeaf = std::min(a + sElementsPerLeaf, numberOfElements); michael@0: for (; srcIndex < srcIndexNextLeaf; srcIndex++) { michael@0: m = std::max(m, mParent.Element(srcIndex)); michael@0: } michael@0: mTreeData[treeIndex] = m; michael@0: treeIndex++; michael@0: } michael@0: } michael@0: michael@0: // Step #2: propagate the values up the tree. This is simply a matter of walking up michael@0: // the tree and setting each node to the max of its two children. michael@0: while (firstTreeIndex > 1) { michael@0: michael@0: // move up 1 level michael@0: firstTreeIndex = ParentNode(firstTreeIndex); michael@0: lastTreeIndex = ParentNode(lastTreeIndex); michael@0: michael@0: // fast-exit case where only one node is invalidated at the current level michael@0: if (firstTreeIndex == lastTreeIndex) { michael@0: mTreeData[firstTreeIndex] = std::max(mTreeData[LeftChildNode(firstTreeIndex)], mTreeData[RightChildNode(firstTreeIndex)]); michael@0: continue; michael@0: } michael@0: michael@0: // initialize local iteration variables: child and parent. michael@0: size_t child = LeftChildNode(firstTreeIndex); michael@0: size_t parent = firstTreeIndex; michael@0: michael@0: // the unrolling makes this look more complicated than it is; the plain non-unrolled michael@0: // version is in the second while loop below michael@0: const int unrollSize = 8; michael@0: while (RightNeighborNode(parent, unrollSize - 1) <= lastTreeIndex) michael@0: { michael@0: for (int unroll = 0; unroll < unrollSize; unroll++) michael@0: { michael@0: T a = mTreeData[child]; michael@0: child = RightNeighborNode(child); michael@0: T b = mTreeData[child]; michael@0: child = RightNeighborNode(child); michael@0: mTreeData[parent] = std::max(a, b); michael@0: parent = RightNeighborNode(parent); michael@0: } michael@0: } michael@0: // plain non-unrolled version, used to terminate the job after the last unrolled iteration michael@0: while (parent <= lastTreeIndex) michael@0: { michael@0: T a = mTreeData[child]; michael@0: child = RightNeighborNode(child); michael@0: T b = mTreeData[child]; michael@0: child = RightNeighborNode(child); michael@0: mTreeData[parent] = std::max(a, b); michael@0: parent = RightNeighborNode(parent); michael@0: } michael@0: } michael@0: michael@0: mInvalidated = false; michael@0: } michael@0: michael@0: WebGLElementArrayCache::~WebGLElementArrayCache() { michael@0: delete mUint8Tree; michael@0: delete mUint16Tree; michael@0: delete mUint32Tree; michael@0: free(mUntypedData); michael@0: } michael@0: michael@0: bool WebGLElementArrayCache::BufferData(const void* ptr, size_t byteSize) { michael@0: mByteSize = byteSize; michael@0: if (mUint8Tree) michael@0: if (!mUint8Tree->ResizeToParentSize()) michael@0: return false; michael@0: if (mUint16Tree) michael@0: if (!mUint16Tree->ResizeToParentSize()) michael@0: return false; michael@0: if (mUint32Tree) michael@0: if (!mUint32Tree->ResizeToParentSize()) michael@0: return false; michael@0: mUntypedData = realloc(mUntypedData, byteSize); michael@0: if (!mUntypedData) michael@0: return false; michael@0: BufferSubData(0, ptr, byteSize); michael@0: return true; michael@0: } michael@0: michael@0: void WebGLElementArrayCache::BufferSubData(size_t pos, const void* ptr, size_t updateByteSize) { michael@0: if (!updateByteSize) return; michael@0: if (ptr) michael@0: memcpy(static_cast(mUntypedData) + pos, ptr, updateByteSize); michael@0: else michael@0: memset(static_cast(mUntypedData) + pos, 0, updateByteSize); michael@0: InvalidateTrees(pos, pos + updateByteSize - 1); michael@0: } michael@0: michael@0: void WebGLElementArrayCache::InvalidateTrees(size_t firstByte, size_t lastByte) michael@0: { michael@0: if (mUint8Tree) michael@0: mUint8Tree->Invalidate(firstByte, lastByte); michael@0: if (mUint16Tree) michael@0: mUint16Tree->Invalidate(firstByte, lastByte); michael@0: if (mUint32Tree) michael@0: mUint32Tree->Invalidate(firstByte, lastByte); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: WebGLElementArrayCache::Validate(uint32_t maxAllowed, size_t firstElement, michael@0: size_t countElements, uint32_t* out_upperBound) michael@0: { michael@0: SetUpperBound(out_upperBound, 0); michael@0: michael@0: // if maxAllowed is >= the max T value, then there is no way that a T index could be invalid michael@0: uint32_t maxTSize = std::numeric_limits::max(); michael@0: if (maxAllowed >= maxTSize) { michael@0: SetUpperBound(out_upperBound, maxTSize); michael@0: return true; michael@0: } michael@0: michael@0: T maxAllowedT(maxAllowed); michael@0: michael@0: // integer overflow must have been handled earlier, so we assert that maxAllowedT michael@0: // is exactly the max allowed value. michael@0: MOZ_ASSERT(uint32_t(maxAllowedT) == maxAllowed); michael@0: michael@0: if (!mByteSize || !countElements) michael@0: return true; michael@0: michael@0: WebGLElementArrayCacheTree*& tree = TreeForType::Run(this); michael@0: if (!tree) { michael@0: tree = new WebGLElementArrayCacheTree(*this); michael@0: } michael@0: michael@0: size_t lastElement = firstElement + countElements - 1; michael@0: michael@0: tree->Update(); michael@0: michael@0: // fast exit path when the global maximum for the whole element array buffer michael@0: // falls in the allowed range michael@0: T globalMax = tree->GlobalMaximum(); michael@0: if (globalMax <= maxAllowedT) michael@0: { michael@0: SetUpperBound(out_upperBound, globalMax); michael@0: return true; michael@0: } michael@0: michael@0: const T* elements = Elements(); michael@0: michael@0: // before calling tree->Validate, we have to validate ourselves the boundaries of the elements span, michael@0: // to round them to the nearest multiple of sElementsPerLeaf. michael@0: size_t firstElementAdjustmentEnd = std::min(lastElement, michael@0: tree->LastElementUnderSameLeaf(firstElement)); michael@0: while (firstElement <= firstElementAdjustmentEnd) { michael@0: const T& curData = elements[firstElement]; michael@0: UpdateUpperBound(out_upperBound, curData); michael@0: if (curData > maxAllowedT) michael@0: return false; michael@0: firstElement++; michael@0: } michael@0: size_t lastElementAdjustmentEnd = std::max(firstElement, michael@0: tree->FirstElementUnderSameLeaf(lastElement)); michael@0: while (lastElement >= lastElementAdjustmentEnd) { michael@0: const T& curData = elements[lastElement]; michael@0: UpdateUpperBound(out_upperBound, curData); michael@0: if (curData > maxAllowedT) michael@0: return false; michael@0: lastElement--; michael@0: } michael@0: michael@0: // at this point, for many tiny validations, we're already done. michael@0: if (firstElement > lastElement) michael@0: return true; michael@0: michael@0: // general case michael@0: return tree->Validate(maxAllowedT, michael@0: tree->LeafForElement(firstElement), michael@0: tree->LeafForElement(lastElement), michael@0: out_upperBound); michael@0: } michael@0: michael@0: bool michael@0: WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed, michael@0: size_t firstElement, size_t countElements, michael@0: uint32_t* out_upperBound) michael@0: { michael@0: if (type == LOCAL_GL_UNSIGNED_BYTE) michael@0: return Validate(maxAllowed, firstElement, countElements, out_upperBound); michael@0: if (type == LOCAL_GL_UNSIGNED_SHORT) michael@0: return Validate(maxAllowed, firstElement, countElements, out_upperBound); michael@0: if (type == LOCAL_GL_UNSIGNED_INT) michael@0: return Validate(maxAllowed, firstElement, countElements, out_upperBound); michael@0: michael@0: MOZ_ASSERT(false, "Invalid type."); michael@0: return false; michael@0: } michael@0: michael@0: size_t michael@0: WebGLElementArrayCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t uint8TreeSize = mUint8Tree ? mUint8Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; michael@0: size_t uint16TreeSize = mUint16Tree ? mUint16Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; michael@0: size_t uint32TreeSize = mUint32Tree ? mUint32Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; michael@0: return aMallocSizeOf(this) + michael@0: mByteSize + michael@0: uint8TreeSize + michael@0: uint16TreeSize + michael@0: uint32TreeSize; michael@0: } michael@0: michael@0: } // end namespace mozilla