|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "WebGLElementArrayCache.h" |
|
7 |
|
8 #include "nsTArray.h" |
|
9 #include "mozilla/Assertions.h" |
|
10 #include "mozilla/MemoryReporting.h" |
|
11 |
|
12 #include <cstdlib> |
|
13 #include <cstring> |
|
14 #include <limits> |
|
15 #include <algorithm> |
|
16 |
|
17 namespace mozilla { |
|
18 |
|
19 static void |
|
20 SetUpperBound(uint32_t* out_upperBound, uint32_t newBound) |
|
21 { |
|
22 if (!out_upperBound) |
|
23 return; |
|
24 |
|
25 *out_upperBound = newBound; |
|
26 } |
|
27 |
|
28 static void |
|
29 UpdateUpperBound(uint32_t* out_upperBound, uint32_t newBound) |
|
30 { |
|
31 if (!out_upperBound) |
|
32 return; |
|
33 |
|
34 *out_upperBound = std::max(*out_upperBound, newBound); |
|
35 } |
|
36 |
|
37 /* |
|
38 * WebGLElementArrayCacheTree contains most of the implementation of WebGLElementArrayCache, |
|
39 * which performs WebGL element array buffer validation for drawElements. |
|
40 * |
|
41 * Attention: Here lie nontrivial data structures, bug-prone algorithms, and non-canonical tweaks! |
|
42 * Whence the explanatory comments, and compiled unit test. |
|
43 * |
|
44 * *** What problem are we solving here? *** |
|
45 * |
|
46 * WebGL::DrawElements has to validate that the elements are in range wrt the current vertex attribs. |
|
47 * This boils down to the problem, given an array of integers, of computing the maximum in an arbitrary |
|
48 * sub-array. The naive algorithm has linear complexity; this has been a major performance problem, |
|
49 * see bug 569431. In that bug, we took the approach of caching the max for the whole array, which |
|
50 * does cover most cases (DrawElements typically consumes the whole element array buffer) but doesn't |
|
51 * help in other use cases: |
|
52 * - when doing "partial DrawElements" i.e. consuming only part of the element array buffer |
|
53 * - when doing frequent "partial buffer updates" i.e. bufferSubData calls updating parts of the |
|
54 * element array buffer |
|
55 * |
|
56 * *** The solution: a binary tree *** |
|
57 * |
|
58 * The solution implemented here is to use a binary tree as the cache data structure. Each tree node |
|
59 * contains the max of its two children nodes. In this way, finding the maximum in any contiguous sub-array |
|
60 * has log complexity instead of linear complexity. |
|
61 * |
|
62 * Simplistically, if the element array is |
|
63 * |
|
64 * 1 4 3 2 |
|
65 * |
|
66 * then the corresponding tree is |
|
67 * |
|
68 * 4 |
|
69 * _/ \_ |
|
70 * 4 3 |
|
71 * / \ / \ |
|
72 * 1 4 3 2 |
|
73 * |
|
74 * In practice, the bottom-most levels of the tree are both the largest to store (because they |
|
75 * have more nodes), and the least useful performance-wise (because each node in the bottom |
|
76 * levels concerns only few entries in the elements array buffer, it is cheap to compute). |
|
77 * |
|
78 * For this reason, we stop the tree a few levels above, so that each tree leaf actually corresponds |
|
79 * to more than one element array entry. |
|
80 * |
|
81 * The number of levels that we "drop" is |sSkippedBottomTreeLevels| and the number of element array entries |
|
82 * that each leaf corresponds to, is |sElementsPerLeaf|. This being a binary tree, we have |
|
83 * |
|
84 * sElementsPerLeaf = 2 ^ sSkippedBottomTreeLevels. |
|
85 * |
|
86 * *** Storage layout of the binary tree *** |
|
87 * |
|
88 * We take advantage of the specifics of the situation to avoid generalist tree storage and instead |
|
89 * store the tree entries in a vector, mTreeData. |
|
90 * |
|
91 * The number of leaves is given by mNumLeaves, and mTreeData is always a vector of length |
|
92 * |
|
93 * 2 * mNumLeaves. |
|
94 * |
|
95 * Its data layout is as follows: mTreeData[0] is unused, mTreeData[1] is the root node, |
|
96 * then at offsets 2..3 is the tree level immediately below the root node, then at offsets 4..7 |
|
97 * is the tree level below that, etc. |
|
98 * |
|
99 * The figure below illustrates this by writing at each tree node the offset into mTreeData at |
|
100 * which it is stored: |
|
101 * |
|
102 * 1 |
|
103 * _/ \_ |
|
104 * 2 3 |
|
105 * / \ / \ |
|
106 * 4 5 6 7 |
|
107 * ... |
|
108 * |
|
109 * Thus, under the convention that the root level is level 0, we see that level N is stored at offsets |
|
110 * |
|
111 * [ 2^n .. 2^(n+1) - 1 ] |
|
112 * |
|
113 * in mTreeData. Likewise, all the usual tree operations have simple mathematical expressions in |
|
114 * terms of mTreeData offsets, see all the methods such as ParentNode, LeftChildNode, etc. |
|
115 * |
|
116 * *** Design constraint: element types aren't known at buffer-update time *** |
|
117 * |
|
118 * Note that a key constraint that we're operating under, is that we don't know the types of the elements |
|
119 * by the time WebGL bufferData/bufferSubData methods are called. The type of elements is only |
|
120 * specified in the drawElements call. This means that we may potentially have to store caches for |
|
121 * multiple element types, for the same element array buffer. Since we don't know yet how many |
|
122 * element types we'll eventually support (extensions add more), the concern about memory usage is serious. |
|
123 * This is addressed by sSkippedBottomTreeLevels as explained above. Of course, in the typical |
|
124 * case where each element array buffer is only ever used with one type, this is also addressed |
|
125 * by having WebGLElementArrayCache lazily create trees for each type only upon first use. |
|
126 * |
|
127 * Another consequence of this constraint is that when invalidating the trees, we have to invalidate |
|
128 * all existing trees. So if trees for types uint8_t, uint16_t and uint32_t have ever been constructed for this buffer, |
|
129 * every subsequent invalidation will have to invalidate all trees even if one of the types is never |
|
130 * used again. This implies that it is important to minimize the cost of invalidation i.e. |
|
131 * do lazy updates upon use as opposed to immediately updating invalidated trees. This poses a problem: |
|
132 * it is nontrivial to keep track of the part of the tree that's invalidated. The current solution |
|
133 * can only keep track of an invalidated interval, from |mFirstInvalidatedLeaf| to |mLastInvalidatedLeaf|. |
|
134 * The problem is that if one does two small, far-apart partial buffer updates, the resulting invalidated |
|
135 * area is very large even though only a small part of the array really needed to be invalidated. |
|
136 * The real solution to this problem would be to use a smarter data structure to keep track of the |
|
137 * invalidated area, probably an interval tree. Meanwhile, we can probably live with the current situation |
|
138 * as the unfavorable case seems to be a small corner case: in order to run into performance issues, |
|
139 * the number of bufferSubData in between two consecutive draws must be small but greater than 1, and |
|
140 * the partial buffer updates must be small and far apart. Anything else than this corner case |
|
141 * should run fast in the current setting. |
|
142 */ |
|
143 template<typename T> |
|
144 struct WebGLElementArrayCacheTree |
|
145 { |
|
146 // A too-high sSkippedBottomTreeLevels would harm the performance of small drawElements calls |
|
147 // A too-low sSkippedBottomTreeLevels would cause undue memory usage. |
|
148 // The current value has been validated by some benchmarking. See bug 732660. |
|
149 static const size_t sSkippedBottomTreeLevels = 3; |
|
150 static const size_t sElementsPerLeaf = 1 << sSkippedBottomTreeLevels; |
|
151 static const size_t sElementsPerLeafMask = sElementsPerLeaf - 1; // sElementsPerLeaf is POT |
|
152 |
|
153 private: |
|
154 WebGLElementArrayCache& mParent; |
|
155 FallibleTArray<T> mTreeData; |
|
156 size_t mNumLeaves; |
|
157 bool mInvalidated; |
|
158 size_t mFirstInvalidatedLeaf; |
|
159 size_t mLastInvalidatedLeaf; |
|
160 |
|
161 public: |
|
162 WebGLElementArrayCacheTree(WebGLElementArrayCache& p) |
|
163 : mParent(p) |
|
164 , mNumLeaves(0) |
|
165 , mInvalidated(false) |
|
166 , mFirstInvalidatedLeaf(0) |
|
167 , mLastInvalidatedLeaf(0) |
|
168 { |
|
169 ResizeToParentSize(); |
|
170 } |
|
171 |
|
172 T GlobalMaximum() const { |
|
173 MOZ_ASSERT(!mInvalidated); |
|
174 return mTreeData[1]; |
|
175 } |
|
176 |
|
177 // returns the index of the parent node; if treeIndex=1 (the root node), |
|
178 // the return value is 0. |
|
179 static size_t ParentNode(size_t treeIndex) { |
|
180 MOZ_ASSERT(treeIndex > 1); |
|
181 return treeIndex >> 1; |
|
182 } |
|
183 |
|
184 static bool IsRightNode(size_t treeIndex) { |
|
185 MOZ_ASSERT(treeIndex > 1); |
|
186 return treeIndex & 1; |
|
187 } |
|
188 |
|
189 static bool IsLeftNode(size_t treeIndex) { |
|
190 MOZ_ASSERT(treeIndex > 1); |
|
191 return !IsRightNode(treeIndex); |
|
192 } |
|
193 |
|
194 static size_t SiblingNode(size_t treeIndex) { |
|
195 MOZ_ASSERT(treeIndex > 1); |
|
196 return treeIndex ^ 1; |
|
197 } |
|
198 |
|
199 static size_t LeftChildNode(size_t treeIndex) { |
|
200 MOZ_ASSERT(treeIndex); |
|
201 return treeIndex << 1; |
|
202 } |
|
203 |
|
204 static size_t RightChildNode(size_t treeIndex) { |
|
205 MOZ_ASSERT(treeIndex); |
|
206 return SiblingNode(LeftChildNode(treeIndex)); |
|
207 } |
|
208 |
|
209 static size_t LeftNeighborNode(size_t treeIndex, size_t distance = 1) { |
|
210 MOZ_ASSERT(treeIndex > 1); |
|
211 return treeIndex - distance; |
|
212 } |
|
213 |
|
214 static size_t RightNeighborNode(size_t treeIndex, size_t distance = 1) { |
|
215 MOZ_ASSERT(treeIndex > 1); |
|
216 return treeIndex + distance; |
|
217 } |
|
218 |
|
219 size_t LeafForElement(size_t element) { |
|
220 size_t leaf = element / sElementsPerLeaf; |
|
221 MOZ_ASSERT(leaf < mNumLeaves); |
|
222 return leaf; |
|
223 } |
|
224 |
|
225 size_t LeafForByte(size_t byte) { |
|
226 return LeafForElement(byte / sizeof(T)); |
|
227 } |
|
228 |
|
229 // Returns the index, into the tree storage, where a given leaf is stored |
|
230 size_t TreeIndexForLeaf(size_t leaf) { |
|
231 // See above class comment. The tree storage is an array of length 2*mNumLeaves. |
|
232 // The leaves are stored in its second half. |
|
233 return leaf + mNumLeaves; |
|
234 } |
|
235 |
|
236 static size_t LastElementUnderSameLeaf(size_t element) { |
|
237 return element | sElementsPerLeafMask; |
|
238 } |
|
239 |
|
240 static size_t FirstElementUnderSameLeaf(size_t element) { |
|
241 return element & ~sElementsPerLeafMask; |
|
242 } |
|
243 |
|
244 static size_t NextMultipleOfElementsPerLeaf(size_t numElements) { |
|
245 return ((numElements - 1) | sElementsPerLeafMask) + 1; |
|
246 } |
|
247 |
|
248 bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf, |
|
249 uint32_t* out_upperBound) |
|
250 { |
|
251 MOZ_ASSERT(!mInvalidated); |
|
252 |
|
253 size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf); |
|
254 size_t lastTreeIndex = TreeIndexForLeaf(lastLeaf); |
|
255 |
|
256 while (true) { |
|
257 // given that we tweak these values in nontrivial ways, it doesn't hurt to do |
|
258 // this sanity check |
|
259 MOZ_ASSERT(firstTreeIndex <= lastTreeIndex); |
|
260 |
|
261 // final case where there is only 1 node to validate at the current tree level |
|
262 if (lastTreeIndex == firstTreeIndex) { |
|
263 const T& curData = mTreeData[firstTreeIndex]; |
|
264 UpdateUpperBound(out_upperBound, curData); |
|
265 return curData <= maxAllowed; |
|
266 } |
|
267 |
|
268 // if the first node at current tree level is a right node, handle it individually |
|
269 // and replace it with its right neighbor, which is a left node |
|
270 if (IsRightNode(firstTreeIndex)) { |
|
271 const T& curData = mTreeData[firstTreeIndex]; |
|
272 UpdateUpperBound(out_upperBound, curData); |
|
273 if (curData > maxAllowed) |
|
274 return false; |
|
275 firstTreeIndex = RightNeighborNode(firstTreeIndex); |
|
276 } |
|
277 |
|
278 // if the last node at current tree level is a left node, handle it individually |
|
279 // and replace it with its left neighbor, which is a right node |
|
280 if (IsLeftNode(lastTreeIndex)) { |
|
281 const T& curData = mTreeData[lastTreeIndex]; |
|
282 UpdateUpperBound(out_upperBound, curData); |
|
283 if (curData > maxAllowed) |
|
284 return false; |
|
285 lastTreeIndex = LeftNeighborNode(lastTreeIndex); |
|
286 } |
|
287 |
|
288 // at this point it can happen that firstTreeIndex and lastTreeIndex "crossed" each |
|
289 // other. That happens if firstTreeIndex was a right node and lastTreeIndex was its |
|
290 // right neighor: in that case, both above tweaks happened and as a result, they ended |
|
291 // up being swapped: lastTreeIndex is now the _left_ neighbor of firstTreeIndex. |
|
292 // When that happens, there is nothing left to validate. |
|
293 if (lastTreeIndex == LeftNeighborNode(firstTreeIndex)) { |
|
294 return true; |
|
295 } |
|
296 |
|
297 // walk up 1 level |
|
298 firstTreeIndex = ParentNode(firstTreeIndex); |
|
299 lastTreeIndex = ParentNode(lastTreeIndex); |
|
300 } |
|
301 } |
|
302 |
|
303 template<typename U> |
|
304 static U NextPowerOfTwo(U x) { |
|
305 U result = 1; |
|
306 while (result < x) |
|
307 result <<= 1; |
|
308 MOZ_ASSERT(result >= x); |
|
309 MOZ_ASSERT((result & (result - 1)) == 0); |
|
310 return result; |
|
311 } |
|
312 |
|
313 bool ResizeToParentSize() |
|
314 { |
|
315 size_t numberOfElements = mParent.ByteSize() / sizeof(T); |
|
316 size_t requiredNumLeaves = (numberOfElements + sElementsPerLeaf - 1) / sElementsPerLeaf; |
|
317 |
|
318 size_t oldNumLeaves = mNumLeaves; |
|
319 mNumLeaves = NextPowerOfTwo(requiredNumLeaves); |
|
320 Invalidate(0, mParent.ByteSize() - 1); |
|
321 |
|
322 // see class comment for why we the tree storage size is 2 * mNumLeaves |
|
323 if (!mTreeData.SetLength(2 * mNumLeaves)) { |
|
324 return false; |
|
325 } |
|
326 if (mNumLeaves != oldNumLeaves) { |
|
327 memset(mTreeData.Elements(), 0, mTreeData.Length() * sizeof(mTreeData[0])); |
|
328 } |
|
329 return true; |
|
330 } |
|
331 |
|
332 void Invalidate(size_t firstByte, size_t lastByte); |
|
333 |
|
334 void Update(); |
|
335 |
|
336 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
337 { |
|
338 return aMallocSizeOf(this) + mTreeData.SizeOfExcludingThis(aMallocSizeOf); |
|
339 } |
|
340 }; |
|
341 |
|
342 // TreeForType: just a template helper to select the right tree object for a given |
|
343 // element type. |
|
344 template<typename T> |
|
345 struct TreeForType {}; |
|
346 |
|
347 template<> |
|
348 struct TreeForType<uint8_t> |
|
349 { |
|
350 static WebGLElementArrayCacheTree<uint8_t>*& Run(WebGLElementArrayCache *b) { return b->mUint8Tree; } |
|
351 }; |
|
352 |
|
353 template<> |
|
354 struct TreeForType<uint16_t> |
|
355 { |
|
356 static WebGLElementArrayCacheTree<uint16_t>*& Run(WebGLElementArrayCache *b) { return b->mUint16Tree; } |
|
357 }; |
|
358 |
|
359 template<> |
|
360 struct TreeForType<uint32_t> |
|
361 { |
|
362 static WebGLElementArrayCacheTree<uint32_t>*& Run(WebGLElementArrayCache *b) { return b->mUint32Tree; } |
|
363 }; |
|
364 |
|
365 // When the buffer gets updated from firstByte to lastByte, |
|
366 // calling this method will notify the tree accordingly |
|
367 template<typename T> |
|
368 void WebGLElementArrayCacheTree<T>::Invalidate(size_t firstByte, size_t lastByte) |
|
369 { |
|
370 lastByte = std::min(lastByte, mNumLeaves * sElementsPerLeaf * sizeof(T) - 1); |
|
371 if (firstByte > lastByte) { |
|
372 return; |
|
373 } |
|
374 |
|
375 size_t firstLeaf = LeafForByte(firstByte); |
|
376 size_t lastLeaf = LeafForByte(lastByte); |
|
377 |
|
378 if (mInvalidated) { |
|
379 mFirstInvalidatedLeaf = std::min(firstLeaf, mFirstInvalidatedLeaf); |
|
380 mLastInvalidatedLeaf = std::max(lastLeaf, mLastInvalidatedLeaf); |
|
381 } else { |
|
382 mInvalidated = true; |
|
383 mFirstInvalidatedLeaf = firstLeaf; |
|
384 mLastInvalidatedLeaf = lastLeaf; |
|
385 } |
|
386 } |
|
387 |
|
388 |
|
389 // When tree has been partially invalidated, from mFirstInvalidatedLeaf to |
|
390 // mLastInvalidatedLeaf, calling this method will 1) update the leaves in this interval |
|
391 // from the raw buffer data, and 2) propagate this update up the tree |
|
392 template<typename T> |
|
393 void WebGLElementArrayCacheTree<T>::Update() |
|
394 { |
|
395 if (!mInvalidated) { |
|
396 return; |
|
397 } |
|
398 |
|
399 MOZ_ASSERT(mLastInvalidatedLeaf < mNumLeaves); |
|
400 |
|
401 size_t firstTreeIndex = TreeIndexForLeaf(mFirstInvalidatedLeaf); |
|
402 size_t lastTreeIndex = TreeIndexForLeaf(mLastInvalidatedLeaf); |
|
403 |
|
404 // Step #1: initialize the tree leaves from plain buffer data. |
|
405 // That is, each tree leaf must be set to the max of the |sElementsPerLeaf| corresponding |
|
406 // buffer entries. |
|
407 // condition-less scope to prevent leaking this scope's variables into the code below |
|
408 { |
|
409 // treeIndex is the index of the tree leaf we're writing, i.e. the destination index |
|
410 size_t treeIndex = firstTreeIndex; |
|
411 // srcIndex is the index in the source buffer |
|
412 size_t srcIndex = mFirstInvalidatedLeaf * sElementsPerLeaf; |
|
413 size_t numberOfElements = mParent.ByteSize() / sizeof(T); |
|
414 while (treeIndex <= lastTreeIndex) { |
|
415 T m = 0; |
|
416 size_t a = srcIndex; |
|
417 size_t srcIndexNextLeaf = std::min(a + sElementsPerLeaf, numberOfElements); |
|
418 for (; srcIndex < srcIndexNextLeaf; srcIndex++) { |
|
419 m = std::max(m, mParent.Element<T>(srcIndex)); |
|
420 } |
|
421 mTreeData[treeIndex] = m; |
|
422 treeIndex++; |
|
423 } |
|
424 } |
|
425 |
|
426 // Step #2: propagate the values up the tree. This is simply a matter of walking up |
|
427 // the tree and setting each node to the max of its two children. |
|
428 while (firstTreeIndex > 1) { |
|
429 |
|
430 // move up 1 level |
|
431 firstTreeIndex = ParentNode(firstTreeIndex); |
|
432 lastTreeIndex = ParentNode(lastTreeIndex); |
|
433 |
|
434 // fast-exit case where only one node is invalidated at the current level |
|
435 if (firstTreeIndex == lastTreeIndex) { |
|
436 mTreeData[firstTreeIndex] = std::max(mTreeData[LeftChildNode(firstTreeIndex)], mTreeData[RightChildNode(firstTreeIndex)]); |
|
437 continue; |
|
438 } |
|
439 |
|
440 // initialize local iteration variables: child and parent. |
|
441 size_t child = LeftChildNode(firstTreeIndex); |
|
442 size_t parent = firstTreeIndex; |
|
443 |
|
444 // the unrolling makes this look more complicated than it is; the plain non-unrolled |
|
445 // version is in the second while loop below |
|
446 const int unrollSize = 8; |
|
447 while (RightNeighborNode(parent, unrollSize - 1) <= lastTreeIndex) |
|
448 { |
|
449 for (int unroll = 0; unroll < unrollSize; unroll++) |
|
450 { |
|
451 T a = mTreeData[child]; |
|
452 child = RightNeighborNode(child); |
|
453 T b = mTreeData[child]; |
|
454 child = RightNeighborNode(child); |
|
455 mTreeData[parent] = std::max(a, b); |
|
456 parent = RightNeighborNode(parent); |
|
457 } |
|
458 } |
|
459 // plain non-unrolled version, used to terminate the job after the last unrolled iteration |
|
460 while (parent <= lastTreeIndex) |
|
461 { |
|
462 T a = mTreeData[child]; |
|
463 child = RightNeighborNode(child); |
|
464 T b = mTreeData[child]; |
|
465 child = RightNeighborNode(child); |
|
466 mTreeData[parent] = std::max(a, b); |
|
467 parent = RightNeighborNode(parent); |
|
468 } |
|
469 } |
|
470 |
|
471 mInvalidated = false; |
|
472 } |
|
473 |
|
474 WebGLElementArrayCache::~WebGLElementArrayCache() { |
|
475 delete mUint8Tree; |
|
476 delete mUint16Tree; |
|
477 delete mUint32Tree; |
|
478 free(mUntypedData); |
|
479 } |
|
480 |
|
481 bool WebGLElementArrayCache::BufferData(const void* ptr, size_t byteSize) { |
|
482 mByteSize = byteSize; |
|
483 if (mUint8Tree) |
|
484 if (!mUint8Tree->ResizeToParentSize()) |
|
485 return false; |
|
486 if (mUint16Tree) |
|
487 if (!mUint16Tree->ResizeToParentSize()) |
|
488 return false; |
|
489 if (mUint32Tree) |
|
490 if (!mUint32Tree->ResizeToParentSize()) |
|
491 return false; |
|
492 mUntypedData = realloc(mUntypedData, byteSize); |
|
493 if (!mUntypedData) |
|
494 return false; |
|
495 BufferSubData(0, ptr, byteSize); |
|
496 return true; |
|
497 } |
|
498 |
|
499 void WebGLElementArrayCache::BufferSubData(size_t pos, const void* ptr, size_t updateByteSize) { |
|
500 if (!updateByteSize) return; |
|
501 if (ptr) |
|
502 memcpy(static_cast<uint8_t*>(mUntypedData) + pos, ptr, updateByteSize); |
|
503 else |
|
504 memset(static_cast<uint8_t*>(mUntypedData) + pos, 0, updateByteSize); |
|
505 InvalidateTrees(pos, pos + updateByteSize - 1); |
|
506 } |
|
507 |
|
508 void WebGLElementArrayCache::InvalidateTrees(size_t firstByte, size_t lastByte) |
|
509 { |
|
510 if (mUint8Tree) |
|
511 mUint8Tree->Invalidate(firstByte, lastByte); |
|
512 if (mUint16Tree) |
|
513 mUint16Tree->Invalidate(firstByte, lastByte); |
|
514 if (mUint32Tree) |
|
515 mUint32Tree->Invalidate(firstByte, lastByte); |
|
516 } |
|
517 |
|
518 template<typename T> |
|
519 bool |
|
520 WebGLElementArrayCache::Validate(uint32_t maxAllowed, size_t firstElement, |
|
521 size_t countElements, uint32_t* out_upperBound) |
|
522 { |
|
523 SetUpperBound(out_upperBound, 0); |
|
524 |
|
525 // if maxAllowed is >= the max T value, then there is no way that a T index could be invalid |
|
526 uint32_t maxTSize = std::numeric_limits<T>::max(); |
|
527 if (maxAllowed >= maxTSize) { |
|
528 SetUpperBound(out_upperBound, maxTSize); |
|
529 return true; |
|
530 } |
|
531 |
|
532 T maxAllowedT(maxAllowed); |
|
533 |
|
534 // integer overflow must have been handled earlier, so we assert that maxAllowedT |
|
535 // is exactly the max allowed value. |
|
536 MOZ_ASSERT(uint32_t(maxAllowedT) == maxAllowed); |
|
537 |
|
538 if (!mByteSize || !countElements) |
|
539 return true; |
|
540 |
|
541 WebGLElementArrayCacheTree<T>*& tree = TreeForType<T>::Run(this); |
|
542 if (!tree) { |
|
543 tree = new WebGLElementArrayCacheTree<T>(*this); |
|
544 } |
|
545 |
|
546 size_t lastElement = firstElement + countElements - 1; |
|
547 |
|
548 tree->Update(); |
|
549 |
|
550 // fast exit path when the global maximum for the whole element array buffer |
|
551 // falls in the allowed range |
|
552 T globalMax = tree->GlobalMaximum(); |
|
553 if (globalMax <= maxAllowedT) |
|
554 { |
|
555 SetUpperBound(out_upperBound, globalMax); |
|
556 return true; |
|
557 } |
|
558 |
|
559 const T* elements = Elements<T>(); |
|
560 |
|
561 // before calling tree->Validate, we have to validate ourselves the boundaries of the elements span, |
|
562 // to round them to the nearest multiple of sElementsPerLeaf. |
|
563 size_t firstElementAdjustmentEnd = std::min(lastElement, |
|
564 tree->LastElementUnderSameLeaf(firstElement)); |
|
565 while (firstElement <= firstElementAdjustmentEnd) { |
|
566 const T& curData = elements[firstElement]; |
|
567 UpdateUpperBound(out_upperBound, curData); |
|
568 if (curData > maxAllowedT) |
|
569 return false; |
|
570 firstElement++; |
|
571 } |
|
572 size_t lastElementAdjustmentEnd = std::max(firstElement, |
|
573 tree->FirstElementUnderSameLeaf(lastElement)); |
|
574 while (lastElement >= lastElementAdjustmentEnd) { |
|
575 const T& curData = elements[lastElement]; |
|
576 UpdateUpperBound(out_upperBound, curData); |
|
577 if (curData > maxAllowedT) |
|
578 return false; |
|
579 lastElement--; |
|
580 } |
|
581 |
|
582 // at this point, for many tiny validations, we're already done. |
|
583 if (firstElement > lastElement) |
|
584 return true; |
|
585 |
|
586 // general case |
|
587 return tree->Validate(maxAllowedT, |
|
588 tree->LeafForElement(firstElement), |
|
589 tree->LeafForElement(lastElement), |
|
590 out_upperBound); |
|
591 } |
|
592 |
|
593 bool |
|
594 WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed, |
|
595 size_t firstElement, size_t countElements, |
|
596 uint32_t* out_upperBound) |
|
597 { |
|
598 if (type == LOCAL_GL_UNSIGNED_BYTE) |
|
599 return Validate<uint8_t>(maxAllowed, firstElement, countElements, out_upperBound); |
|
600 if (type == LOCAL_GL_UNSIGNED_SHORT) |
|
601 return Validate<uint16_t>(maxAllowed, firstElement, countElements, out_upperBound); |
|
602 if (type == LOCAL_GL_UNSIGNED_INT) |
|
603 return Validate<uint32_t>(maxAllowed, firstElement, countElements, out_upperBound); |
|
604 |
|
605 MOZ_ASSERT(false, "Invalid type."); |
|
606 return false; |
|
607 } |
|
608 |
|
609 size_t |
|
610 WebGLElementArrayCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
611 { |
|
612 size_t uint8TreeSize = mUint8Tree ? mUint8Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; |
|
613 size_t uint16TreeSize = mUint16Tree ? mUint16Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; |
|
614 size_t uint32TreeSize = mUint32Tree ? mUint32Tree->SizeOfIncludingThis(aMallocSizeOf) : 0; |
|
615 return aMallocSizeOf(this) + |
|
616 mByteSize + |
|
617 uint8TreeSize + |
|
618 uint16TreeSize + |
|
619 uint32TreeSize; |
|
620 } |
|
621 |
|
622 } // end namespace mozilla |