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();
21 mIndexCache
= nullptr;
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
= 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 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.");
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
131 memcpy(newIndexCache
.get(), uploadData
, size
);
132 uploadData
= newIndexCache
.get();
135 const auto& gl
= mContext
->gl
;
136 const ScopedLazyBind
lazyBind(gl
, target
, this);
138 const bool sizeChanges
= (size
!= ByteLength());
140 gl::GLContext::LocalErrorScope
errorScope(*gl
);
141 gl
->fBufferData(target
, size
, uploadData
, usage
);
142 const auto error
= errorScope
.GetError();
145 MOZ_ASSERT(error
== LOCAL_GL_OUT_OF_MEMORY
);
146 mContext
->ErrorOutOfMemory("Error from driver: 0x%04x", error
);
150 mFetchInvalidator
.InvalidateCaches();
151 mIndexCache
= nullptr;
155 gl
->fBufferData(target
, size
, uploadData
, usage
);
158 mContext
->OnDataAllocCall();
162 mFetchInvalidator
.InvalidateCaches();
163 mIndexCache
= std::move(newIndexCache
);
166 if (!mIndexRanges
.empty()) {
167 mContext
->GeneratePerfWarning("[%p] Invalidating %u ranges.", this,
168 uint32_t(mIndexRanges
.size()));
169 mIndexRanges
.clear();
173 ResetLastUpdateFenceId();
176 void WebGLBuffer::BufferSubData(GLenum target
, uint64_t dstByteOffset
,
177 uint64_t dataLen
, const void* data
) const {
178 if (!ValidateRange(dstByteOffset
, dataLen
)) return;
180 if (!CheckedInt
<GLintptr
>(dstByteOffset
).isValid() ||
181 !CheckedInt
<GLsizeiptr
>(dataLen
).isValid())
182 return mContext
->ErrorOutOfMemory("offset or size too large for platform.");
186 const void* uploadData
= data
;
188 const auto cachedDataBegin
= (uint8_t*)mIndexCache
.get() + dstByteOffset
;
189 memcpy(cachedDataBegin
, data
, dataLen
);
190 uploadData
= cachedDataBegin
;
192 InvalidateCacheRange(dstByteOffset
, dataLen
);
197 const auto& gl
= mContext
->gl
;
198 const ScopedLazyBind
lazyBind(gl
, target
, this);
200 gl
->fBufferSubData(target
, dstByteOffset
, dataLen
, uploadData
);
202 ResetLastUpdateFenceId();
205 bool WebGLBuffer::ValidateRange(size_t byteOffset
, size_t byteLen
) const {
206 auto availLength
= mByteLength
;
207 if (byteOffset
> availLength
) {
208 mContext
->ErrorInvalidValue("Offset passes the end of the buffer.");
211 availLength
-= byteOffset
;
213 if (byteLen
> availLength
) {
214 mContext
->ErrorInvalidValue("Offset+size passes the end of the buffer.");
221 ////////////////////////////////////////
223 static uint8_t IndexByteSizeByType(GLenum type
) {
225 case LOCAL_GL_UNSIGNED_BYTE
:
227 case LOCAL_GL_UNSIGNED_SHORT
:
229 case LOCAL_GL_UNSIGNED_INT
:
236 void WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset
,
237 uint64_t byteLength
) const {
238 MOZ_ASSERT(mIndexCache
);
240 std::vector
<IndexRange
> invalids
;
241 const uint64_t updateBegin
= byteOffset
;
242 const uint64_t updateEnd
= updateBegin
+ byteLength
;
243 for (const auto& cur
: mIndexRanges
) {
244 const auto& range
= cur
.first
;
245 const auto& indexByteSize
= IndexByteSizeByType(range
.type
);
246 const auto rangeBegin
= range
.byteOffset
* indexByteSize
;
247 const auto rangeEnd
=
248 rangeBegin
+ uint64_t(range
.indexCount
) * indexByteSize
;
249 if (rangeBegin
>= updateEnd
|| rangeEnd
<= updateBegin
) continue;
250 invalids
.push_back(range
);
253 if (!invalids
.empty()) {
254 mContext
->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
255 uint32_t(invalids
.size()),
256 uint32_t(mIndexRanges
.size()));
258 for (const auto& cur
: invalids
) {
259 mIndexRanges
.erase(cur
);
264 size_t WebGLBuffer::SizeOfIncludingThis(
265 mozilla::MallocSizeOf mallocSizeOf
) const {
266 size_t size
= mallocSizeOf(this);
273 template <typename T
>
274 static Maybe
<uint32_t> MaxForRange(const void* const start
,
275 const uint32_t count
,
276 const Maybe
<uint32_t>& untypedIgnoredVal
) {
277 const Maybe
<T
> ignoredVal
=
278 (untypedIgnoredVal
? Some(T(untypedIgnoredVal
.value())) : Nothing());
279 Maybe
<uint32_t> maxVal
;
281 auto itr
= (const T
*)start
;
282 const auto end
= itr
+ count
;
284 for (; itr
!= end
; ++itr
) {
285 const auto& val
= *itr
;
286 if (ignoredVal
&& val
== ignoredVal
.value()) continue;
288 if (maxVal
&& val
<= maxVal
.value()) continue;
296 static const uint32_t kMaxIndexRanges
= 256;
298 Maybe
<uint32_t> WebGLBuffer::GetIndexedFetchMaxVert(
299 const GLenum type
, const uint64_t byteOffset
,
300 const uint32_t indexCount
) const {
301 if (!mIndexCache
) return Nothing();
303 const IndexRange range
= {type
, byteOffset
, indexCount
};
304 auto res
= mIndexRanges
.insert({range
, Nothing()});
305 if (mIndexRanges
.size() > kMaxIndexRanges
) {
306 mContext
->GeneratePerfWarning(
307 "[%p] Clearing mIndexRanges after exceeding %u.", this,
309 mIndexRanges
.clear();
310 res
= mIndexRanges
.insert({range
, Nothing()});
313 const auto& itr
= res
.first
;
314 const auto& didInsert
= res
.second
;
316 auto& maxFetchIndex
= itr
->second
;
318 const auto& data
= mIndexCache
.get();
320 const auto start
= (const uint8_t*)data
+ byteOffset
;
322 Maybe
<uint32_t> ignoredVal
;
323 if (mContext
->IsWebGL2()) {
324 ignoredVal
= Some(UINT32_MAX
);
328 case LOCAL_GL_UNSIGNED_BYTE
:
329 maxFetchIndex
= MaxForRange
<uint8_t>(start
, indexCount
, ignoredVal
);
331 case LOCAL_GL_UNSIGNED_SHORT
:
332 maxFetchIndex
= MaxForRange
<uint16_t>(start
, indexCount
, ignoredVal
);
334 case LOCAL_GL_UNSIGNED_INT
:
335 maxFetchIndex
= MaxForRange
<uint32_t>(start
, indexCount
, ignoredVal
);
340 const auto displayMaxVertIndex
=
341 maxFetchIndex
? int64_t(maxFetchIndex
.value()) : -1;
342 mContext
->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64
345 this, uint32_t(mIndexRanges
.size()),
346 range
.type
, range
.byteOffset
,
347 range
.indexCount
, displayMaxVertIndex
);
350 return maxFetchIndex
;
355 bool WebGLBuffer::ValidateCanBindToTarget(GLenum target
) {
356 /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
358 * In the WebGL 2 API, buffers have their WebGL buffer type
359 * initially set to undefined. Calling bindBuffer, bindBufferRange
360 * or bindBufferBase with the target argument set to any buffer
361 * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
362 * then set the WebGL buffer type of the buffer being bound
363 * according to the table above.
365 * Any call to one of these functions which attempts to bind a
366 * WebGLBuffer that has the element array WebGL buffer type to a
367 * binding point that falls under other data, or bind a
368 * WebGLBuffer which has the other data WebGL buffer type to
369 * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
370 * and the state of the binding point will remain untouched.
373 if (mContent
== WebGLBuffer::Kind::Undefined
) return true;
376 case LOCAL_GL_COPY_READ_BUFFER
:
377 case LOCAL_GL_COPY_WRITE_BUFFER
:
380 case LOCAL_GL_ELEMENT_ARRAY_BUFFER
:
381 if (mContent
== WebGLBuffer::Kind::ElementArray
) return true;
384 case LOCAL_GL_ARRAY_BUFFER
:
385 case LOCAL_GL_PIXEL_PACK_BUFFER
:
386 case LOCAL_GL_PIXEL_UNPACK_BUFFER
:
387 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER
:
388 case LOCAL_GL_UNIFORM_BUFFER
:
389 if (mContent
== WebGLBuffer::Kind::OtherData
) return true;
396 const auto dataType
=
397 (mContent
== WebGLBuffer::Kind::OtherData
) ? "other" : "element";
398 mContext
->ErrorInvalidOperation("Buffer already contains %s data.", dataType
);
402 void WebGLBuffer::ResetLastUpdateFenceId() const {
403 mLastUpdateFenceId
= mContext
->mNextFenceId
;
406 } // namespace mozilla