/src/mozilla-central/dom/canvas/WebGLBuffer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 20; 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 "WebGLBuffer.h" |
7 | | |
8 | | #include "GLContext.h" |
9 | | #include "mozilla/dom/WebGLRenderingContextBinding.h" |
10 | | #include "WebGLContext.h" |
11 | | |
12 | | namespace mozilla { |
13 | | |
14 | | WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf) |
15 | | : WebGLRefCountedObject(webgl) |
16 | | , mGLName(buf) |
17 | | , mContent(Kind::Undefined) |
18 | | , mUsage(LOCAL_GL_STATIC_DRAW) |
19 | | , mByteLength(0) |
20 | | , mTFBindCount(0) |
21 | | , mNonTFBindCount(0) |
22 | 0 | { |
23 | 0 | mContext->mBuffers.insertBack(this); |
24 | 0 | } |
25 | | |
26 | | WebGLBuffer::~WebGLBuffer() |
27 | 0 | { |
28 | 0 | DeleteOnce(); |
29 | 0 | } |
30 | | |
31 | | void |
32 | | WebGLBuffer::SetContentAfterBind(GLenum target) |
33 | 0 | { |
34 | 0 | if (mContent != Kind::Undefined) |
35 | 0 | return; |
36 | 0 | |
37 | 0 | switch (target) { |
38 | 0 | case LOCAL_GL_ELEMENT_ARRAY_BUFFER: |
39 | 0 | mContent = Kind::ElementArray; |
40 | 0 | break; |
41 | 0 |
|
42 | 0 | case LOCAL_GL_ARRAY_BUFFER: |
43 | 0 | case LOCAL_GL_PIXEL_PACK_BUFFER: |
44 | 0 | case LOCAL_GL_PIXEL_UNPACK_BUFFER: |
45 | 0 | case LOCAL_GL_UNIFORM_BUFFER: |
46 | 0 | case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: |
47 | 0 | case LOCAL_GL_COPY_READ_BUFFER: |
48 | 0 | case LOCAL_GL_COPY_WRITE_BUFFER: |
49 | 0 | mContent = Kind::OtherData; |
50 | 0 | break; |
51 | 0 |
|
52 | 0 | default: |
53 | 0 | MOZ_CRASH("GFX: invalid target"); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | | void |
58 | | WebGLBuffer::Delete() |
59 | 0 | { |
60 | 0 | mContext->gl->fDeleteBuffers(1, &mGLName); |
61 | 0 |
|
62 | 0 | mByteLength = 0; |
63 | 0 | mFetchInvalidator.InvalidateCaches(); |
64 | 0 |
|
65 | 0 | mIndexCache = nullptr; |
66 | 0 | mIndexRanges.clear(); |
67 | 0 | LinkedListElement<WebGLBuffer>::remove(); // remove from mContext->mBuffers |
68 | 0 | } |
69 | | |
70 | | //////////////////////////////////////// |
71 | | |
72 | | static bool |
73 | | ValidateBufferUsageEnum(WebGLContext* webgl, GLenum usage) |
74 | 0 | { |
75 | 0 | switch (usage) { |
76 | 0 | case LOCAL_GL_STREAM_DRAW: |
77 | 0 | case LOCAL_GL_STATIC_DRAW: |
78 | 0 | case LOCAL_GL_DYNAMIC_DRAW: |
79 | 0 | return true; |
80 | 0 |
|
81 | 0 | case LOCAL_GL_DYNAMIC_COPY: |
82 | 0 | case LOCAL_GL_DYNAMIC_READ: |
83 | 0 | case LOCAL_GL_STATIC_COPY: |
84 | 0 | case LOCAL_GL_STATIC_READ: |
85 | 0 | case LOCAL_GL_STREAM_COPY: |
86 | 0 | case LOCAL_GL_STREAM_READ: |
87 | 0 | if (MOZ_LIKELY(webgl->IsWebGL2())) |
88 | 0 | return true; |
89 | 0 | break; |
90 | 0 |
|
91 | 0 | default: |
92 | 0 | break; |
93 | 0 | } |
94 | 0 | |
95 | 0 | webgl->ErrorInvalidEnumInfo("usage", usage); |
96 | 0 | return false; |
97 | 0 | } |
98 | | |
99 | | void |
100 | | WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usage) |
101 | 0 | { |
102 | 0 | // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr |
103 | 0 | // is like intptr_t. |
104 | 0 | if (!CheckedInt<GLsizeiptr>(size).isValid()) |
105 | 0 | return mContext->ErrorOutOfMemory("bad size"); |
106 | 0 | |
107 | 0 | if (!ValidateBufferUsageEnum(mContext, usage)) |
108 | 0 | return; |
109 | 0 | |
110 | | #ifdef XP_MACOSX |
111 | | // bug 790879 |
112 | | if (mContext->gl->WorkAroundDriverBugs() && |
113 | | size > INT32_MAX) |
114 | | { |
115 | | mContext->ErrorOutOfMemory("Allocation size too large."); |
116 | | return; |
117 | | } |
118 | | #endif |
119 | | |
120 | 0 | const void* uploadData = data; |
121 | 0 |
|
122 | 0 | UniqueBuffer newIndexCache; |
123 | 0 | if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER && |
124 | 0 | mContext->mNeedsIndexValidation) |
125 | 0 | { |
126 | 0 | newIndexCache = malloc(size); |
127 | 0 | if (!newIndexCache) { |
128 | 0 | mContext->ErrorOutOfMemory("Failed to alloc index cache."); |
129 | 0 | return; |
130 | 0 | } |
131 | 0 | memcpy(newIndexCache.get(), data, size); |
132 | 0 | uploadData = newIndexCache.get(); |
133 | 0 | } |
134 | 0 |
|
135 | 0 | const auto& gl = mContext->gl; |
136 | 0 | const ScopedLazyBind lazyBind(gl, target, this); |
137 | 0 |
|
138 | 0 | const bool sizeChanges = (size != ByteLength()); |
139 | 0 | if (sizeChanges) { |
140 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
141 | 0 | gl->fBufferData(target, size, uploadData, usage); |
142 | 0 | const auto error = errorScope.GetError(); |
143 | 0 |
|
144 | 0 | if (error) { |
145 | 0 | MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY); |
146 | 0 | mContext->ErrorOutOfMemory("Error from driver: 0x%04x", error); |
147 | 0 | return; |
148 | 0 | } |
149 | 0 | } else { |
150 | 0 | gl->fBufferData(target, size, uploadData, usage); |
151 | 0 | } |
152 | 0 |
|
153 | 0 | mContext->OnDataAllocCall(); |
154 | 0 |
|
155 | 0 | mUsage = usage; |
156 | 0 | mByteLength = size; |
157 | 0 | mFetchInvalidator.InvalidateCaches(); |
158 | 0 | mIndexCache = std::move(newIndexCache); |
159 | 0 |
|
160 | 0 | if (mIndexCache) { |
161 | 0 | if (!mIndexRanges.empty()) { |
162 | 0 | mContext->GeneratePerfWarning("[%p] Invalidating %u ranges.", this, |
163 | 0 | uint32_t(mIndexRanges.size())); |
164 | 0 | mIndexRanges.clear(); |
165 | 0 | } |
166 | 0 | } |
167 | 0 |
|
168 | 0 | ResetLastUpdateFenceId(); |
169 | 0 | } |
170 | | |
171 | | void |
172 | | WebGLBuffer::BufferSubData(GLenum target, size_t dstByteOffset, size_t dataLen, |
173 | | const void* data) const |
174 | 0 | { |
175 | 0 | if (!ValidateRange(dstByteOffset, dataLen)) |
176 | 0 | return; |
177 | 0 | |
178 | 0 | if (!CheckedInt<GLintptr>(dataLen).isValid()) |
179 | 0 | return mContext->ErrorOutOfMemory("Size too large."); |
180 | 0 | |
181 | 0 | //// |
182 | 0 | |
183 | 0 | const void* uploadData = data; |
184 | 0 | if (mIndexCache) { |
185 | 0 | const auto cachedDataBegin = (uint8_t*)mIndexCache.get() + dstByteOffset; |
186 | 0 | memcpy(cachedDataBegin, data, dataLen); |
187 | 0 | uploadData = cachedDataBegin; |
188 | 0 |
|
189 | 0 | InvalidateCacheRange(dstByteOffset, dataLen); |
190 | 0 | } |
191 | 0 |
|
192 | 0 | //// |
193 | 0 |
|
194 | 0 | const auto& gl = mContext->gl; |
195 | 0 | const ScopedLazyBind lazyBind(gl, target, this); |
196 | 0 |
|
197 | 0 | gl->fBufferSubData(target, dstByteOffset, dataLen, uploadData); |
198 | 0 |
|
199 | 0 | ResetLastUpdateFenceId(); |
200 | 0 | } |
201 | | |
202 | | bool |
203 | | WebGLBuffer::ValidateRange(size_t byteOffset, size_t byteLen) const |
204 | 0 | { |
205 | 0 | auto availLength = mByteLength; |
206 | 0 | if (byteOffset > availLength) { |
207 | 0 | mContext->ErrorInvalidValue("Offset passes the end of the buffer."); |
208 | 0 | return false; |
209 | 0 | } |
210 | 0 | availLength -= byteOffset; |
211 | 0 |
|
212 | 0 | if (byteLen > availLength) { |
213 | 0 | mContext->ErrorInvalidValue("Offset+size passes the end of the buffer."); |
214 | 0 | return false; |
215 | 0 | } |
216 | 0 | |
217 | 0 | return true; |
218 | 0 | } |
219 | | |
220 | | //////////////////////////////////////// |
221 | | |
222 | | static uint8_t |
223 | | IndexByteSizeByType(GLenum type) |
224 | 0 | { |
225 | 0 | switch (type) { |
226 | 0 | case LOCAL_GL_UNSIGNED_BYTE: return 1; |
227 | 0 | case LOCAL_GL_UNSIGNED_SHORT: return 2; |
228 | 0 | case LOCAL_GL_UNSIGNED_INT: return 4; |
229 | 0 | default: |
230 | 0 | MOZ_CRASH(); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | void |
235 | | WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset, uint64_t byteLength) const |
236 | 0 | { |
237 | 0 | MOZ_ASSERT(mIndexCache); |
238 | 0 |
|
239 | 0 | std::vector<IndexRange> invalids; |
240 | 0 | const uint64_t updateBegin = byteOffset; |
241 | 0 | const uint64_t updateEnd = updateBegin + byteLength; |
242 | 0 | for (const auto& cur : mIndexRanges) { |
243 | 0 | const auto& range = cur.first; |
244 | 0 | const auto& indexByteSize = IndexByteSizeByType(range.type); |
245 | 0 | const auto rangeBegin = range.byteOffset * indexByteSize; |
246 | 0 | const auto rangeEnd = rangeBegin + uint64_t(range.indexCount) * indexByteSize; |
247 | 0 | if (rangeBegin >= updateEnd || rangeEnd <= updateBegin) |
248 | 0 | continue; |
249 | 0 | invalids.push_back(range); |
250 | 0 | } |
251 | 0 |
|
252 | 0 | if (!invalids.empty()) { |
253 | 0 | mContext->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this, |
254 | 0 | uint32_t(invalids.size()), |
255 | 0 | uint32_t(mIndexRanges.size())); |
256 | 0 |
|
257 | 0 | for (const auto& cur : invalids) { |
258 | 0 | mIndexRanges.erase(cur); |
259 | 0 | } |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | | size_t |
264 | | WebGLBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const |
265 | 0 | { |
266 | 0 | size_t size = mallocSizeOf(this); |
267 | 0 | if (mIndexCache) { |
268 | 0 | size += mByteLength; |
269 | 0 | } |
270 | 0 | return size; |
271 | 0 | } |
272 | | |
273 | | template<typename T> |
274 | | static Maybe<uint32_t> |
275 | | MaxForRange(const void* const start, const uint32_t count, |
276 | | const Maybe<uint32_t>& untypedIgnoredVal) |
277 | 0 | { |
278 | 0 | const Maybe<T> ignoredVal = (untypedIgnoredVal ? Some(T(untypedIgnoredVal.value())) |
279 | 0 | : Nothing()); |
280 | 0 | Maybe<uint32_t> maxVal; |
281 | 0 |
|
282 | 0 | auto itr = (const T*)start; |
283 | 0 | const auto end = itr + count; |
284 | 0 |
|
285 | 0 | for (; itr != end; ++itr) { |
286 | 0 | const auto& val = *itr; |
287 | 0 | if (ignoredVal && val == ignoredVal.value()) |
288 | 0 | continue; |
289 | 0 | |
290 | 0 | if (maxVal && val <= maxVal.value()) |
291 | 0 | continue; |
292 | 0 | |
293 | 0 | maxVal = Some(val); |
294 | 0 | } |
295 | 0 |
|
296 | 0 | return maxVal; |
297 | 0 | } Unexecuted instantiation: Unified_cpp_dom_canvas1.cpp:mozilla::Maybe<unsigned int> mozilla::MaxForRange<unsigned char>(void const*, unsigned int, mozilla::Maybe<unsigned int> const&) Unexecuted instantiation: Unified_cpp_dom_canvas1.cpp:mozilla::Maybe<unsigned int> mozilla::MaxForRange<unsigned short>(void const*, unsigned int, mozilla::Maybe<unsigned int> const&) Unexecuted instantiation: Unified_cpp_dom_canvas1.cpp:mozilla::Maybe<unsigned int> mozilla::MaxForRange<unsigned int>(void const*, unsigned int, mozilla::Maybe<unsigned int> const&) |
298 | | |
299 | | static const uint32_t kMaxIndexRanges = 256; |
300 | | |
301 | | Maybe<uint32_t> |
302 | | WebGLBuffer::GetIndexedFetchMaxVert(const GLenum type, const uint64_t byteOffset, |
303 | | const uint32_t indexCount) const |
304 | 0 | { |
305 | 0 | if (!mIndexCache) |
306 | 0 | return Nothing(); |
307 | 0 | |
308 | 0 | const IndexRange range = { type, byteOffset, indexCount }; |
309 | 0 | auto res = mIndexRanges.insert({ range, Nothing() }); |
310 | 0 | if (mIndexRanges.size() > kMaxIndexRanges) { |
311 | 0 | mContext->GeneratePerfWarning("[%p] Clearing mIndexRanges after exceeding %u.", |
312 | 0 | this, kMaxIndexRanges); |
313 | 0 | mIndexRanges.clear(); |
314 | 0 | res = mIndexRanges.insert({ range, Nothing() }); |
315 | 0 | } |
316 | 0 |
|
317 | 0 | const auto& itr = res.first; |
318 | 0 | const auto& didInsert = res.second; |
319 | 0 |
|
320 | 0 | auto& maxFetchIndex = itr->second; |
321 | 0 | if (didInsert) { |
322 | 0 | const auto& data = mIndexCache.get(); |
323 | 0 |
|
324 | 0 | const auto start = (const uint8_t*)data + byteOffset; |
325 | 0 |
|
326 | 0 | Maybe<uint32_t> ignoredVal; |
327 | 0 | if (mContext->IsWebGL2()) { |
328 | 0 | ignoredVal = Some(UINT32_MAX); |
329 | 0 | } |
330 | 0 |
|
331 | 0 | switch (type) { |
332 | 0 | case LOCAL_GL_UNSIGNED_BYTE: |
333 | 0 | maxFetchIndex = MaxForRange<uint8_t>(start, indexCount, ignoredVal); |
334 | 0 | break; |
335 | 0 | case LOCAL_GL_UNSIGNED_SHORT: |
336 | 0 | maxFetchIndex = MaxForRange<uint16_t>(start, indexCount, ignoredVal); |
337 | 0 | break; |
338 | 0 | case LOCAL_GL_UNSIGNED_INT: |
339 | 0 | maxFetchIndex = MaxForRange<uint32_t>(start, indexCount, ignoredVal); |
340 | 0 | break; |
341 | 0 | default: |
342 | 0 | MOZ_CRASH(); |
343 | 0 | } |
344 | 0 | const auto displayMaxVertIndex = maxFetchIndex ? int64_t(maxFetchIndex.value()) |
345 | 0 | : -1; |
346 | 0 | mContext->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64 ", %u):" |
347 | 0 | " %" PRIi64, |
348 | 0 | this, uint32_t(mIndexRanges.size()), range.type, |
349 | 0 | range.byteOffset, range.indexCount, |
350 | 0 | displayMaxVertIndex); |
351 | 0 | } |
352 | 0 |
|
353 | 0 | return maxFetchIndex; |
354 | 0 | } |
355 | | |
356 | | //// |
357 | | |
358 | | bool |
359 | | WebGLBuffer::ValidateCanBindToTarget(GLenum target) |
360 | 0 | { |
361 | 0 | /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1 |
362 | 0 | * |
363 | 0 | * In the WebGL 2 API, buffers have their WebGL buffer type |
364 | 0 | * initially set to undefined. Calling bindBuffer, bindBufferRange |
365 | 0 | * or bindBufferBase with the target argument set to any buffer |
366 | 0 | * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will |
367 | 0 | * then set the WebGL buffer type of the buffer being bound |
368 | 0 | * according to the table above. |
369 | 0 | * |
370 | 0 | * Any call to one of these functions which attempts to bind a |
371 | 0 | * WebGLBuffer that has the element array WebGL buffer type to a |
372 | 0 | * binding point that falls under other data, or bind a |
373 | 0 | * WebGLBuffer which has the other data WebGL buffer type to |
374 | 0 | * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error, |
375 | 0 | * and the state of the binding point will remain untouched. |
376 | 0 | */ |
377 | 0 |
|
378 | 0 | if (mContent == WebGLBuffer::Kind::Undefined) |
379 | 0 | return true; |
380 | 0 | |
381 | 0 | switch (target) { |
382 | 0 | case LOCAL_GL_COPY_READ_BUFFER: |
383 | 0 | case LOCAL_GL_COPY_WRITE_BUFFER: |
384 | 0 | return true; |
385 | 0 |
|
386 | 0 | case LOCAL_GL_ELEMENT_ARRAY_BUFFER: |
387 | 0 | if (mContent == WebGLBuffer::Kind::ElementArray) |
388 | 0 | return true; |
389 | 0 | break; |
390 | 0 |
|
391 | 0 | case LOCAL_GL_ARRAY_BUFFER: |
392 | 0 | case LOCAL_GL_PIXEL_PACK_BUFFER: |
393 | 0 | case LOCAL_GL_PIXEL_UNPACK_BUFFER: |
394 | 0 | case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER: |
395 | 0 | case LOCAL_GL_UNIFORM_BUFFER: |
396 | 0 | if (mContent == WebGLBuffer::Kind::OtherData) |
397 | 0 | return true; |
398 | 0 | break; |
399 | 0 |
|
400 | 0 | default: |
401 | 0 | MOZ_CRASH(); |
402 | 0 | } |
403 | 0 |
|
404 | 0 | const auto dataType = (mContent == WebGLBuffer::Kind::OtherData) ? "other" |
405 | 0 | : "element"; |
406 | 0 | mContext->ErrorInvalidOperation("Buffer already contains %s data.", |
407 | 0 | dataType); |
408 | 0 | return false; |
409 | 0 | } |
410 | | |
411 | | void |
412 | | WebGLBuffer::ResetLastUpdateFenceId() const |
413 | 0 | { |
414 | 0 | mLastUpdateFenceId = mContext->mNextFenceId; |
415 | 0 | } |
416 | | |
417 | | JSObject* |
418 | | WebGLBuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) |
419 | 0 | { |
420 | 0 | return dom::WebGLBuffer_Binding::Wrap(cx, this, givenProto); |
421 | 0 | } |
422 | | |
423 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBuffer) |
424 | | |
425 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLBuffer, AddRef) |
426 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLBuffer, Release) |
427 | | |
428 | | } // namespace mozilla |