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