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"
9 #include "mozilla/dom/WebGLRenderingContextBinding.h"
10 #include "WebGLContext.h"
14 WebGLBuffer::WebGLBuffer(WebGLContext
* webgl
, GLuint buf
)
15 : WebGLContextBoundObject(webgl
), mGLName(buf
) {}
17 WebGLBuffer::~WebGLBuffer() {
19 mFetchInvalidator
.InvalidateCaches();
24 if (!mContext
) return;
25 mContext
->gl
->fDeleteBuffers(1, &mGLName
);
28 void WebGLBuffer::SetContentAfterBind(GLenum target
) {
29 if (mContent
!= Kind::Undefined
) return;
32 case LOCAL_GL_ELEMENT_ARRAY_BUFFER
:
33 mContent
= Kind::ElementArray
;
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
;
47 MOZ_CRASH("GFX: invalid target");
51 ////////////////////////////////////////
53 static bool ValidateBufferUsageEnum(WebGLContext
* webgl
, GLenum usage
) {
55 case LOCAL_GL_STREAM_DRAW
:
56 case LOCAL_GL_STATIC_DRAW
:
57 case LOCAL_GL_DYNAMIC_DRAW
:
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;
73 webgl
->ErrorInvalidEnumInfo("usage", usage
);
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()) {
85 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
86 sizeValid
&= CheckedInt
<int32_t>(size
).isValid();
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();
98 mContext
->ErrorOutOfMemory("Size not valid for platform: %" PRIu64
, size
);
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
)));
111 mContext
->ErrorOutOfMemory("Failed to alloc zeros.");
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.");
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
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());
142 gl::GLContext::LocalErrorScope
errorScope(*gl
);
143 gl
->fBufferData(target
, size
, uploadData
, usage
);
144 const auto error
= errorScope
.GetError();
147 MOZ_ASSERT(error
== LOCAL_GL_OUT_OF_MEMORY
);
148 mContext
->ErrorOutOfMemory("Error from driver: 0x%04x", error
);
152 mFetchInvalidator
.InvalidateCaches();
157 gl
->fBufferData(target
, size
, uploadData
, usage
);
160 mContext
->OnDataAllocCall();
164 mFetchInvalidator
.InvalidateCaches();
165 mIndexCache
= std::move(newIndexCache
);
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.");
191 if (!rawDataLen
) return; // With validation successful, nothing else to do.
193 const void* uploadData
= data
;
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());
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
) &&
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
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
);
228 memcpy(mapping
, uploadData
, dataLen
.value());
229 gl
->fUnmapBuffer(target
);
231 gl
->fBufferSubData(target
, dstByteOffset
.value(), dataLen
.value(),
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.");
244 availLength
-= byteOffset
;
246 if (byteLen
> availLength
) {
247 mContext
->ErrorInvalidValue("Offset+size passes the end of the buffer.");
254 ////////////////////////////////////////
256 static uint8_t IndexByteSizeByType(GLenum type
) {
258 case LOCAL_GL_UNSIGNED_BYTE
:
260 case LOCAL_GL_UNSIGNED_SHORT
:
262 case LOCAL_GL_UNSIGNED_INT
:
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);
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;
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,
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
;
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
);
361 case LOCAL_GL_UNSIGNED_BYTE
:
362 maxFetchIndex
= MaxForRange
<uint8_t>(start
, indexCount
, ignoredVal
);
364 case LOCAL_GL_UNSIGNED_SHORT
:
365 maxFetchIndex
= MaxForRange
<uint16_t>(start
, indexCount
, ignoredVal
);
367 case LOCAL_GL_UNSIGNED_INT
:
368 maxFetchIndex
= MaxForRange
<uint32_t>(start
, indexCount
, ignoredVal
);
373 const auto displayMaxVertIndex
=
374 maxFetchIndex
? int64_t(maxFetchIndex
.value()) : -1;
375 mContext
->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64
378 this, uint32_t(mIndexRanges
.size()),
379 range
.type
, range
.byteOffset
,
380 range
.indexCount
, displayMaxVertIndex
);
383 return maxFetchIndex
;
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;
409 case LOCAL_GL_COPY_READ_BUFFER
:
410 case LOCAL_GL_COPY_WRITE_BUFFER
:
413 case LOCAL_GL_ELEMENT_ARRAY_BUFFER
:
414 if (mContent
== WebGLBuffer::Kind::ElementArray
) return true;
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;
429 const auto dataType
=
430 (mContent
== WebGLBuffer::Kind::OtherData
) ? "other" : "element";
431 mContext
->ErrorInvalidOperation("Buffer already contains %s data.", dataType
);
435 void WebGLBuffer::ResetLastUpdateFenceId() const {
436 mLastUpdateFenceId
= mContext
->mNextFenceId
;
439 } // namespace mozilla