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