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 if (!dataLen
) return; // With validation successful, nothing else to do.
188 const void* uploadData
= data
;
190 const auto cachedDataBegin
= (uint8_t*)mIndexCache
.get() + dstByteOffset
;
191 memcpy(cachedDataBegin
, data
, dataLen
);
192 uploadData
= cachedDataBegin
;
194 InvalidateCacheRange(dstByteOffset
, dataLen
);
199 const auto& gl
= mContext
->gl
;
200 const ScopedLazyBind
lazyBind(gl
, target
, this);
202 gl
->fBufferSubData(target
, dstByteOffset
, dataLen
, uploadData
);
204 ResetLastUpdateFenceId();
207 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.");
213 availLength
-= byteOffset
;
215 if (byteLen
> availLength
) {
216 mContext
->ErrorInvalidValue("Offset+size passes the end of the buffer.");
223 ////////////////////////////////////////
225 static uint8_t IndexByteSizeByType(GLenum type
) {
227 case LOCAL_GL_UNSIGNED_BYTE
:
229 case LOCAL_GL_UNSIGNED_SHORT
:
231 case LOCAL_GL_UNSIGNED_INT
:
238 void WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset
,
239 uint64_t byteLength
) const {
240 MOZ_ASSERT(mIndexCache
);
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
);
255 if (!invalids
.empty()) {
256 mContext
->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
257 uint32_t(invalids
.size()),
258 uint32_t(mIndexRanges
.size()));
260 for (const auto& cur
: invalids
) {
261 mIndexRanges
.erase(cur
);
266 size_t WebGLBuffer::SizeOfIncludingThis(
267 mozilla::MallocSizeOf mallocSizeOf
) const {
268 size_t size
= mallocSizeOf(this);
275 template <typename T
>
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
;
283 auto itr
= (const T
*)start
;
284 const auto end
= itr
+ count
;
286 for (; itr
!= end
; ++itr
) {
287 const auto& val
= *itr
;
288 if (ignoredVal
&& val
== ignoredVal
.value()) continue;
290 if (maxVal
&& val
<= maxVal
.value()) continue;
298 static const uint32_t kMaxIndexRanges
= 256;
300 Maybe
<uint32_t> WebGLBuffer::GetIndexedFetchMaxVert(
301 const GLenum type
, const uint64_t byteOffset
,
302 const uint32_t indexCount
) const {
303 if (!mIndexCache
) return Nothing();
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,
311 mIndexRanges
.clear();
312 res
= mIndexRanges
.insert({range
, Nothing()});
315 const auto& itr
= res
.first
;
316 const auto& didInsert
= res
.second
;
318 auto& maxFetchIndex
= itr
->second
;
320 const auto& data
= mIndexCache
.get();
322 const auto start
= (const uint8_t*)data
+ byteOffset
;
324 Maybe
<uint32_t> ignoredVal
;
325 if (mContext
->IsWebGL2()) {
326 ignoredVal
= Some(UINT32_MAX
);
330 case LOCAL_GL_UNSIGNED_BYTE
:
331 maxFetchIndex
= MaxForRange
<uint8_t>(start
, indexCount
, ignoredVal
);
333 case LOCAL_GL_UNSIGNED_SHORT
:
334 maxFetchIndex
= MaxForRange
<uint16_t>(start
, indexCount
, ignoredVal
);
336 case LOCAL_GL_UNSIGNED_INT
:
337 maxFetchIndex
= MaxForRange
<uint32_t>(start
, indexCount
, ignoredVal
);
342 const auto displayMaxVertIndex
=
343 maxFetchIndex
? int64_t(maxFetchIndex
.value()) : -1;
344 mContext
->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64
347 this, uint32_t(mIndexRanges
.size()),
348 range
.type
, range
.byteOffset
,
349 range
.indexCount
, displayMaxVertIndex
);
352 return maxFetchIndex
;
357 bool WebGLBuffer::ValidateCanBindToTarget(GLenum target
) {
358 /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
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.
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.
375 if (mContent
== WebGLBuffer::Kind::Undefined
) return true;
378 case LOCAL_GL_COPY_READ_BUFFER
:
379 case LOCAL_GL_COPY_WRITE_BUFFER
:
382 case LOCAL_GL_ELEMENT_ARRAY_BUFFER
:
383 if (mContent
== WebGLBuffer::Kind::ElementArray
) return true;
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;
398 const auto dataType
=
399 (mContent
== WebGLBuffer::Kind::OtherData
) ? "other" : "element";
400 mContext
->ErrorInvalidOperation("Buffer already contains %s data.", dataType
);
404 void WebGLBuffer::ResetLastUpdateFenceId() const {
405 mLastUpdateFenceId
= mContext
->mNextFenceId
;
408 } // namespace mozilla