Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / canvas / WebGLBuffer.cpp
blob86ba0351954ae1776069444f621b2317289f574b
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/. */
6 #include "WebGLBuffer.h"
8 #include "GLContext.h"
9 #include "mozilla/dom/WebGLRenderingContextBinding.h"
10 #include "WebGLContext.h"
12 namespace mozilla {
14 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
15 : WebGLContextBoundObject(webgl), mGLName(buf) {}
17 WebGLBuffer::~WebGLBuffer() {
18 mByteLength = 0;
19 mFetchInvalidator.InvalidateCaches();
21 mIndexCache.reset();
22 mIndexRanges.clear();
24 if (!mContext) return;
25 mContext->gl->fDeleteBuffers(1, &mGLName);
28 void WebGLBuffer::SetContentAfterBind(GLenum target) {
29 if (mContent != Kind::Undefined) return;
31 switch (target) {
32 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
33 mContent = Kind::ElementArray;
34 break;
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;
46 default:
47 MOZ_CRASH("GFX: invalid target");
51 ////////////////////////////////////////
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;
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;
69 default:
70 break;
73 webgl->ErrorInvalidEnumInfo("usage", usage);
74 return false;
77 void WebGLBuffer::BufferData(const GLenum target, const uint64_t size,
78 const void* const maybeData, const GLenum usage,
79 bool allowUninitialized) {
80 // The driver knows only GLsizeiptr, which is int32_t on 32bit!
81 bool sizeValid = CheckedInt<GLsizeiptr>(size).isValid();
83 if (mContext->gl->WorkAroundDriverBugs()) {
84 // Bug 790879
85 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
86 sizeValid &= CheckedInt<int32_t>(size).isValid();
87 #endif
89 // Bug 1610383
90 if (mContext->gl->IsANGLE()) {
91 // While ANGLE seems to support up to `unsigned int`, UINT32_MAX-4 causes
92 // GL_OUT_OF_MEMORY in glFlush??
93 sizeValid &= CheckedInt<int32_t>(size).isValid();
97 if (!sizeValid) {
98 mContext->ErrorOutOfMemory("Size not valid for platform: %" PRIu64, size);
99 return;
102 // -
104 if (!ValidateBufferUsageEnum(mContext, usage)) return;
106 const void* uploadData = maybeData;
107 UniqueBuffer maybeCalloc;
108 if (!uploadData && !allowUninitialized) {
109 maybeCalloc = UniqueBuffer::Take(calloc(1, AssertedCast<size_t>(size)));
110 if (!maybeCalloc) {
111 mContext->ErrorOutOfMemory("Failed to alloc zeros.");
112 return;
114 uploadData = maybeCalloc.get();
116 MOZ_ASSERT(uploadData || allowUninitialized);
118 UniqueBuffer newIndexCache;
119 const bool needsIndexCache = mContext->mNeedsIndexValidation ||
120 mContext->mMaybeNeedsLegacyVertexAttrib0Handling;
121 if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER && needsIndexCache) {
122 newIndexCache = UniqueBuffer::Take(malloc(AssertedCast<size_t>(size)));
123 if (!newIndexCache) {
124 mContext->ErrorOutOfMemory("Failed to alloc index cache.");
125 return;
127 // memcpy out of SharedArrayBuffers can be racey, and should generally use
128 // memcpySafeWhenRacy. But it's safe here:
129 // * We only memcpy in one place.
130 // * We only read out of the single copy, and only after copying.
131 // * If we get data value corruption from racing read-during-write, that's
132 // fine.
133 memcpy(newIndexCache.get(), uploadData, size);
134 uploadData = newIndexCache.get();
137 const auto& gl = mContext->gl;
138 const ScopedLazyBind lazyBind(gl, target, this);
140 const bool sizeChanges = (size != ByteLength());
141 if (sizeChanges) {
142 gl::GLContext::LocalErrorScope errorScope(*gl);
143 gl->fBufferData(target, size, uploadData, usage);
144 const auto error = errorScope.GetError();
146 if (error) {
147 MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY);
148 mContext->ErrorOutOfMemory("Error from driver: 0x%04x", error);
150 // Truncate
151 mByteLength = 0;
152 mFetchInvalidator.InvalidateCaches();
153 mIndexCache.reset();
154 return;
156 } else {
157 gl->fBufferData(target, size, uploadData, usage);
160 mContext->OnDataAllocCall();
162 mUsage = usage;
163 mByteLength = size;
164 mFetchInvalidator.InvalidateCaches();
165 mIndexCache = std::move(newIndexCache);
167 if (mIndexCache) {
168 if (!mIndexRanges.empty()) {
169 mContext->GeneratePerfWarning("[%p] Invalidating %u ranges.", this,
170 uint32_t(mIndexRanges.size()));
171 mIndexRanges.clear();
175 ResetLastUpdateFenceId();
178 void WebGLBuffer::BufferSubData(GLenum target, uint64_t rawDstByteOffset,
179 uint64_t rawDataLen, const void* data,
180 bool unsynchronized) const {
181 if (!ValidateRange(rawDstByteOffset, rawDataLen)) return;
183 const CheckedInt<GLintptr> dstByteOffset = rawDstByteOffset;
184 const CheckedInt<GLsizeiptr> dataLen = rawDataLen;
185 if (!dstByteOffset.isValid() || !dataLen.isValid()) {
186 return mContext->ErrorOutOfMemory("offset or size too large for platform.");
189 ////
191 if (!rawDataLen) return; // With validation successful, nothing else to do.
193 const void* uploadData = data;
194 if (mIndexCache) {
195 auto* const cachedDataBegin =
196 (uint8_t*)mIndexCache.get() + rawDstByteOffset;
197 memcpy(cachedDataBegin, data, dataLen.value());
198 uploadData = cachedDataBegin;
200 InvalidateCacheRange(dstByteOffset.value(), dataLen.value());
203 ////
205 const auto& gl = mContext->gl;
206 const ScopedLazyBind lazyBind(gl, target, this);
208 void* mapping = nullptr;
209 // Repeated calls to glMapBufferRange is slow on ANGLE, so fall back to the
210 // glBufferSubData path. See bug 1827047.
211 if (unsynchronized && gl->IsSupported(gl::GLFeature::map_buffer_range) &&
212 !gl->IsANGLE()) {
213 GLbitfield access = LOCAL_GL_MAP_WRITE_BIT |
214 LOCAL_GL_MAP_UNSYNCHRONIZED_BIT |
215 LOCAL_GL_MAP_INVALIDATE_RANGE_BIT;
216 // On some devices there are known performance issues with the combination
217 // of GL_MAP_UNSYNCHRONIZED_BIT and GL_MAP_INVALIDATE_RANGE_BIT, so omit the
218 // latter.
219 if (gl->Renderer() == gl::GLRenderer::MaliT ||
220 gl->Vendor() == gl::GLVendor::Qualcomm) {
221 access &= ~LOCAL_GL_MAP_INVALIDATE_RANGE_BIT;
223 mapping = gl->fMapBufferRange(target, dstByteOffset.value(),
224 dataLen.value(), access);
227 if (mapping) {
228 memcpy(mapping, uploadData, dataLen.value());
229 gl->fUnmapBuffer(target);
230 } else {
231 gl->fBufferSubData(target, dstByteOffset.value(), dataLen.value(),
232 uploadData);
235 ResetLastUpdateFenceId();
238 bool WebGLBuffer::ValidateRange(size_t byteOffset, size_t byteLen) const {
239 auto availLength = mByteLength;
240 if (byteOffset > availLength) {
241 mContext->ErrorInvalidValue("Offset passes the end of the buffer.");
242 return false;
244 availLength -= byteOffset;
246 if (byteLen > availLength) {
247 mContext->ErrorInvalidValue("Offset+size passes the end of the buffer.");
248 return false;
251 return true;
254 ////////////////////////////////////////
256 static uint8_t IndexByteSizeByType(GLenum type) {
257 switch (type) {
258 case LOCAL_GL_UNSIGNED_BYTE:
259 return 1;
260 case LOCAL_GL_UNSIGNED_SHORT:
261 return 2;
262 case LOCAL_GL_UNSIGNED_INT:
263 return 4;
264 default:
265 MOZ_CRASH();
269 void WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset,
270 uint64_t byteLength) const {
271 MOZ_ASSERT(mIndexCache);
273 std::vector<IndexRange> invalids;
274 const uint64_t updateBegin = byteOffset;
275 const uint64_t updateEnd = updateBegin + byteLength;
276 for (const auto& cur : mIndexRanges) {
277 const auto& range = cur.first;
278 const auto& indexByteSize = IndexByteSizeByType(range.type);
279 const auto rangeBegin = range.byteOffset * indexByteSize;
280 const auto rangeEnd =
281 rangeBegin + uint64_t(range.indexCount) * indexByteSize;
282 if (rangeBegin >= updateEnd || rangeEnd <= updateBegin) continue;
283 invalids.push_back(range);
286 if (!invalids.empty()) {
287 mContext->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
288 uint32_t(invalids.size()),
289 uint32_t(mIndexRanges.size()));
291 for (const auto& cur : invalids) {
292 mIndexRanges.erase(cur);
297 size_t WebGLBuffer::SizeOfIncludingThis(
298 mozilla::MallocSizeOf mallocSizeOf) const {
299 size_t size = mallocSizeOf(this);
300 if (mIndexCache) {
301 size += mByteLength;
303 return size;
306 template <typename T>
307 static Maybe<uint32_t> MaxForRange(const void* const start,
308 const uint32_t count,
309 const Maybe<uint32_t>& untypedIgnoredVal) {
310 const Maybe<T> ignoredVal =
311 (untypedIgnoredVal ? Some(T(untypedIgnoredVal.value())) : Nothing());
312 Maybe<uint32_t> maxVal;
314 auto itr = (const T*)start;
315 const auto end = itr + count;
317 for (; itr != end; ++itr) {
318 const auto& val = *itr;
319 if (ignoredVal && val == ignoredVal.value()) continue;
321 if (maxVal && val <= maxVal.value()) continue;
323 maxVal = Some(val);
326 return maxVal;
329 static const uint32_t kMaxIndexRanges = 256;
331 Maybe<uint32_t> WebGLBuffer::GetIndexedFetchMaxVert(
332 const GLenum type, const uint64_t byteOffset,
333 const uint32_t indexCount) const {
334 if (!mIndexCache) return Nothing();
336 const IndexRange range = {type, byteOffset, indexCount};
337 auto res = mIndexRanges.insert({range, Nothing()});
338 if (mIndexRanges.size() > kMaxIndexRanges) {
339 mContext->GeneratePerfWarning(
340 "[%p] Clearing mIndexRanges after exceeding %u.", this,
341 kMaxIndexRanges);
342 mIndexRanges.clear();
343 res = mIndexRanges.insert({range, Nothing()});
346 const auto& itr = res.first;
347 const auto& didInsert = res.second;
349 auto& maxFetchIndex = itr->second;
350 if (didInsert) {
351 const auto& data = mIndexCache.get();
353 const auto start = (const uint8_t*)data + byteOffset;
355 Maybe<uint32_t> ignoredVal;
356 if (mContext->IsWebGL2()) {
357 ignoredVal = Some(UINT32_MAX);
360 switch (type) {
361 case LOCAL_GL_UNSIGNED_BYTE:
362 maxFetchIndex = MaxForRange<uint8_t>(start, indexCount, ignoredVal);
363 break;
364 case LOCAL_GL_UNSIGNED_SHORT:
365 maxFetchIndex = MaxForRange<uint16_t>(start, indexCount, ignoredVal);
366 break;
367 case LOCAL_GL_UNSIGNED_INT:
368 maxFetchIndex = MaxForRange<uint32_t>(start, indexCount, ignoredVal);
369 break;
370 default:
371 MOZ_CRASH();
373 const auto displayMaxVertIndex =
374 maxFetchIndex ? int64_t(maxFetchIndex.value()) : -1;
375 mContext->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64
376 ", %u):"
377 " %" PRIi64,
378 this, uint32_t(mIndexRanges.size()),
379 range.type, range.byteOffset,
380 range.indexCount, displayMaxVertIndex);
383 return maxFetchIndex;
386 ////
388 bool WebGLBuffer::ValidateCanBindToTarget(GLenum target) {
389 /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
391 * In the WebGL 2 API, buffers have their WebGL buffer type
392 * initially set to undefined. Calling bindBuffer, bindBufferRange
393 * or bindBufferBase with the target argument set to any buffer
394 * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
395 * then set the WebGL buffer type of the buffer being bound
396 * according to the table above.
398 * Any call to one of these functions which attempts to bind a
399 * WebGLBuffer that has the element array WebGL buffer type to a
400 * binding point that falls under other data, or bind a
401 * WebGLBuffer which has the other data WebGL buffer type to
402 * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
403 * and the state of the binding point will remain untouched.
406 if (mContent == WebGLBuffer::Kind::Undefined) return true;
408 switch (target) {
409 case LOCAL_GL_COPY_READ_BUFFER:
410 case LOCAL_GL_COPY_WRITE_BUFFER:
411 return true;
413 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
414 if (mContent == WebGLBuffer::Kind::ElementArray) return true;
415 break;
417 case LOCAL_GL_ARRAY_BUFFER:
418 case LOCAL_GL_PIXEL_PACK_BUFFER:
419 case LOCAL_GL_PIXEL_UNPACK_BUFFER:
420 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
421 case LOCAL_GL_UNIFORM_BUFFER:
422 if (mContent == WebGLBuffer::Kind::OtherData) return true;
423 break;
425 default:
426 MOZ_CRASH();
429 const auto dataType =
430 (mContent == WebGLBuffer::Kind::OtherData) ? "other" : "element";
431 mContext->ErrorInvalidOperation("Buffer already contains %s data.", dataType);
432 return false;
435 void WebGLBuffer::ResetLastUpdateFenceId() const {
436 mLastUpdateFenceId = mContext->mNextFenceId;
439 } // namespace mozilla