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 // The driver knows only GLsizeiptr, which is int32_t on 32bit!
80 bool sizeValid
= CheckedInt
<GLsizeiptr
>(size
).isValid();
82 if (mContext
->gl
->WorkAroundDriverBugs()) {
84 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
85 sizeValid
&= CheckedInt
<int32_t>(size
).isValid();
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();
97 mContext
->ErrorOutOfMemory("Size not valid for platform: %" PRIu64
, size
);
103 if (!ValidateBufferUsageEnum(mContext
, usage
)) return;
105 const void* uploadData
= maybeData
;
106 UniqueBuffer maybeCalloc
;
108 maybeCalloc
= UniqueBuffer::Take(calloc(1, AssertedCast
<size_t>(size
)));
110 mContext
->ErrorOutOfMemory("Failed to alloc zeros.");
113 uploadData
= maybeCalloc
.get();
115 MOZ_ASSERT(uploadData
);
117 UniqueBuffer newIndexCache
;
118 const bool needsIndexCache
= mContext
->mNeedsIndexValidation
||
119 mContext
->mMaybeNeedsLegacyVertexAttrib0Handling
;
120 if (target
== LOCAL_GL_ELEMENT_ARRAY_BUFFER
&& needsIndexCache
) {
121 newIndexCache
= UniqueBuffer::Take(malloc(AssertedCast
<size_t>(size
)));
122 if (!newIndexCache
) {
123 mContext
->ErrorOutOfMemory("Failed to alloc index cache.");
126 // memcpy out of SharedArrayBuffers can be racey, and should generally use
127 // memcpySafeWhenRacy. But it's safe here:
128 // * We only memcpy in one place.
129 // * We only read out of the single copy, and only after copying.
130 // * If we get data value corruption from racing read-during-write, that's
132 memcpy(newIndexCache
.get(), uploadData
, size
);
133 uploadData
= newIndexCache
.get();
136 const auto& gl
= mContext
->gl
;
137 const ScopedLazyBind
lazyBind(gl
, target
, this);
139 const bool sizeChanges
= (size
!= ByteLength());
141 gl::GLContext::LocalErrorScope
errorScope(*gl
);
142 gl
->fBufferData(target
, size
, uploadData
, usage
);
143 const auto error
= errorScope
.GetError();
146 MOZ_ASSERT(error
== LOCAL_GL_OUT_OF_MEMORY
);
147 mContext
->ErrorOutOfMemory("Error from driver: 0x%04x", error
);
151 mFetchInvalidator
.InvalidateCaches();
156 gl
->fBufferData(target
, size
, uploadData
, usage
);
159 mContext
->OnDataAllocCall();
163 mFetchInvalidator
.InvalidateCaches();
164 mIndexCache
= std::move(newIndexCache
);
167 if (!mIndexRanges
.empty()) {
168 mContext
->GeneratePerfWarning("[%p] Invalidating %u ranges.", this,
169 uint32_t(mIndexRanges
.size()));
170 mIndexRanges
.clear();
174 ResetLastUpdateFenceId();
177 void WebGLBuffer::BufferSubData(GLenum target
, uint64_t rawDstByteOffset
,
178 uint64_t rawDataLen
, const void* data
,
179 bool unsynchronized
) const {
180 if (!ValidateRange(rawDstByteOffset
, rawDataLen
)) return;
182 const CheckedInt
<GLintptr
> dstByteOffset
= rawDstByteOffset
;
183 const CheckedInt
<GLsizeiptr
> dataLen
= rawDataLen
;
184 if (!dstByteOffset
.isValid() || !dataLen
.isValid()) {
185 return mContext
->ErrorOutOfMemory("offset or size too large for platform.");
190 if (!rawDataLen
) return; // With validation successful, nothing else to do.
192 const void* uploadData
= data
;
194 auto* const cachedDataBegin
=
195 (uint8_t*)mIndexCache
.get() + rawDstByteOffset
;
196 memcpy(cachedDataBegin
, data
, dataLen
.value());
197 uploadData
= cachedDataBegin
;
199 InvalidateCacheRange(dstByteOffset
.value(), dataLen
.value());
204 const auto& gl
= mContext
->gl
;
205 const ScopedLazyBind
lazyBind(gl
, target
, this);
207 void* mapping
= nullptr;
208 // Repeated calls to glMapBufferRange is slow on ANGLE, so fall back to the
209 // glBufferSubData path. See bug 1827047.
210 if (unsynchronized
&& gl
->IsSupported(gl::GLFeature::map_buffer_range
) &&
212 GLbitfield access
= LOCAL_GL_MAP_WRITE_BIT
|
213 LOCAL_GL_MAP_UNSYNCHRONIZED_BIT
|
214 LOCAL_GL_MAP_INVALIDATE_RANGE_BIT
;
215 // On some devices there are known performance issues with the combination
216 // of GL_MAP_UNSYNCHRONIZED_BIT and GL_MAP_INVALIDATE_RANGE_BIT, so omit the
218 if (gl
->Renderer() == gl::GLRenderer::MaliT
||
219 gl
->Vendor() == gl::GLVendor::Qualcomm
) {
220 access
&= ~LOCAL_GL_MAP_INVALIDATE_RANGE_BIT
;
222 mapping
= gl
->fMapBufferRange(target
, dstByteOffset
.value(),
223 dataLen
.value(), access
);
227 memcpy(mapping
, uploadData
, dataLen
.value());
228 gl
->fUnmapBuffer(target
);
230 gl
->fBufferSubData(target
, dstByteOffset
.value(), dataLen
.value(),
234 ResetLastUpdateFenceId();
237 bool WebGLBuffer::ValidateRange(size_t byteOffset
, size_t byteLen
) const {
238 auto availLength
= mByteLength
;
239 if (byteOffset
> availLength
) {
240 mContext
->ErrorInvalidValue("Offset passes the end of the buffer.");
243 availLength
-= byteOffset
;
245 if (byteLen
> availLength
) {
246 mContext
->ErrorInvalidValue("Offset+size passes the end of the buffer.");
253 ////////////////////////////////////////
255 static uint8_t IndexByteSizeByType(GLenum type
) {
257 case LOCAL_GL_UNSIGNED_BYTE
:
259 case LOCAL_GL_UNSIGNED_SHORT
:
261 case LOCAL_GL_UNSIGNED_INT
:
268 void WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset
,
269 uint64_t byteLength
) const {
270 MOZ_ASSERT(mIndexCache
);
272 std::vector
<IndexRange
> invalids
;
273 const uint64_t updateBegin
= byteOffset
;
274 const uint64_t updateEnd
= updateBegin
+ byteLength
;
275 for (const auto& cur
: mIndexRanges
) {
276 const auto& range
= cur
.first
;
277 const auto& indexByteSize
= IndexByteSizeByType(range
.type
);
278 const auto rangeBegin
= range
.byteOffset
* indexByteSize
;
279 const auto rangeEnd
=
280 rangeBegin
+ uint64_t(range
.indexCount
) * indexByteSize
;
281 if (rangeBegin
>= updateEnd
|| rangeEnd
<= updateBegin
) continue;
282 invalids
.push_back(range
);
285 if (!invalids
.empty()) {
286 mContext
->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
287 uint32_t(invalids
.size()),
288 uint32_t(mIndexRanges
.size()));
290 for (const auto& cur
: invalids
) {
291 mIndexRanges
.erase(cur
);
296 size_t WebGLBuffer::SizeOfIncludingThis(
297 mozilla::MallocSizeOf mallocSizeOf
) const {
298 size_t size
= mallocSizeOf(this);
305 template <typename T
>
306 static Maybe
<uint32_t> MaxForRange(const void* const start
,
307 const uint32_t count
,
308 const Maybe
<uint32_t>& untypedIgnoredVal
) {
309 const Maybe
<T
> ignoredVal
=
310 (untypedIgnoredVal
? Some(T(untypedIgnoredVal
.value())) : Nothing());
311 Maybe
<uint32_t> maxVal
;
313 auto itr
= (const T
*)start
;
314 const auto end
= itr
+ count
;
316 for (; itr
!= end
; ++itr
) {
317 const auto& val
= *itr
;
318 if (ignoredVal
&& val
== ignoredVal
.value()) continue;
320 if (maxVal
&& val
<= maxVal
.value()) continue;
328 static const uint32_t kMaxIndexRanges
= 256;
330 Maybe
<uint32_t> WebGLBuffer::GetIndexedFetchMaxVert(
331 const GLenum type
, const uint64_t byteOffset
,
332 const uint32_t indexCount
) const {
333 if (!mIndexCache
) return Nothing();
335 const IndexRange range
= {type
, byteOffset
, indexCount
};
336 auto res
= mIndexRanges
.insert({range
, Nothing()});
337 if (mIndexRanges
.size() > kMaxIndexRanges
) {
338 mContext
->GeneratePerfWarning(
339 "[%p] Clearing mIndexRanges after exceeding %u.", this,
341 mIndexRanges
.clear();
342 res
= mIndexRanges
.insert({range
, Nothing()});
345 const auto& itr
= res
.first
;
346 const auto& didInsert
= res
.second
;
348 auto& maxFetchIndex
= itr
->second
;
350 const auto& data
= mIndexCache
.get();
352 const auto start
= (const uint8_t*)data
+ byteOffset
;
354 Maybe
<uint32_t> ignoredVal
;
355 if (mContext
->IsWebGL2()) {
356 ignoredVal
= Some(UINT32_MAX
);
360 case LOCAL_GL_UNSIGNED_BYTE
:
361 maxFetchIndex
= MaxForRange
<uint8_t>(start
, indexCount
, ignoredVal
);
363 case LOCAL_GL_UNSIGNED_SHORT
:
364 maxFetchIndex
= MaxForRange
<uint16_t>(start
, indexCount
, ignoredVal
);
366 case LOCAL_GL_UNSIGNED_INT
:
367 maxFetchIndex
= MaxForRange
<uint32_t>(start
, indexCount
, ignoredVal
);
372 const auto displayMaxVertIndex
=
373 maxFetchIndex
? int64_t(maxFetchIndex
.value()) : -1;
374 mContext
->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64
377 this, uint32_t(mIndexRanges
.size()),
378 range
.type
, range
.byteOffset
,
379 range
.indexCount
, displayMaxVertIndex
);
382 return maxFetchIndex
;
387 bool WebGLBuffer::ValidateCanBindToTarget(GLenum target
) {
388 /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
390 * In the WebGL 2 API, buffers have their WebGL buffer type
391 * initially set to undefined. Calling bindBuffer, bindBufferRange
392 * or bindBufferBase with the target argument set to any buffer
393 * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
394 * then set the WebGL buffer type of the buffer being bound
395 * according to the table above.
397 * Any call to one of these functions which attempts to bind a
398 * WebGLBuffer that has the element array WebGL buffer type to a
399 * binding point that falls under other data, or bind a
400 * WebGLBuffer which has the other data WebGL buffer type to
401 * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
402 * and the state of the binding point will remain untouched.
405 if (mContent
== WebGLBuffer::Kind::Undefined
) return true;
408 case LOCAL_GL_COPY_READ_BUFFER
:
409 case LOCAL_GL_COPY_WRITE_BUFFER
:
412 case LOCAL_GL_ELEMENT_ARRAY_BUFFER
:
413 if (mContent
== WebGLBuffer::Kind::ElementArray
) return true;
416 case LOCAL_GL_ARRAY_BUFFER
:
417 case LOCAL_GL_PIXEL_PACK_BUFFER
:
418 case LOCAL_GL_PIXEL_UNPACK_BUFFER
:
419 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
:
420 case LOCAL_GL_UNIFORM_BUFFER
:
421 if (mContent
== WebGLBuffer::Kind::OtherData
) return true;
428 const auto dataType
=
429 (mContent
== WebGLBuffer::Kind::OtherData
) ? "other" : "element";
430 mContext
->ErrorInvalidOperation("Buffer already contains %s data.", dataType
);
434 void WebGLBuffer::ResetLastUpdateFenceId() const {
435 mLastUpdateFenceId
= mContext
->mNextFenceId
;
438 } // namespace mozilla