WebGLBuffer.cpp (13550B)
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/. */ 5 6 #include "WebGLBuffer.h" 7 8 #include "GLContext.h" 9 #include "WebGLContext.h" 10 #include "mozilla/dom/WebGLRenderingContextBinding.h" 11 12 namespace mozilla { 13 14 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf) 15 : WebGLContextBoundObject(webgl), mGLName(buf) {} 16 17 WebGLBuffer::~WebGLBuffer() { 18 mByteLength = 0; 19 mFetchInvalidator.InvalidateCaches(); 20 21 mIndexCache.reset(); 22 mIndexRanges.clear(); 23 24 if (!mContext) return; 25 mContext->gl->fDeleteBuffers(1, &mGLName); 26 } 27 28 void WebGLBuffer::SetContentAfterBind(GLenum target) { 29 if (mContent != Kind::Undefined) return; 30 31 switch (target) { 32 case LOCAL_GL_ELEMENT_ARRAY_BUFFER: 33 mContent = Kind::ElementArray; 34 break; 35 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; 45 46 default: 47 MOZ_CRASH("GFX: invalid target"); 48 } 49 } 50 51 //////////////////////////////////////// 52 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; 59 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; 68 69 default: 70 break; 71 } 72 73 webgl->ErrorInvalidEnumInfo("usage", usage); 74 return false; 75 } 76 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(); 82 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 88 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(); 94 } 95 } 96 97 if (!sizeValid) { 98 mContext->ErrorOutOfMemory("Size not valid for platform: %" PRIu64, size); 99 return; 100 } 101 102 // - 103 104 if (!ValidateBufferUsageEnum(mContext, usage)) return; 105 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; 113 } 114 uploadData = maybeCalloc.get(); 115 } 116 MOZ_ASSERT(uploadData || allowUninitialized); 117 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; 126 } 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(); 135 } 136 137 const auto& gl = mContext->gl; 138 const ScopedLazyBind lazyBind(gl, target, this); 139 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(); 145 146 if (error) { 147 MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY); 148 mContext->ErrorOutOfMemory("Error from driver: 0x%04x", error); 149 150 // Truncate 151 mByteLength = 0; 152 mFetchInvalidator.InvalidateCaches(); 153 mIndexCache.reset(); 154 return; 155 } 156 } else { 157 gl->fBufferData(target, size, uploadData, usage); 158 } 159 160 mContext->OnDataAllocCall(); 161 162 mUsage = usage; 163 mByteLength = size; 164 mFetchInvalidator.InvalidateCaches(); 165 mIndexCache = std::move(newIndexCache); 166 167 if (mIndexCache) { 168 if (!mIndexRanges.empty()) { 169 mContext->GeneratePerfWarning("[%p] Invalidating %u ranges.", this, 170 uint32_t(mIndexRanges.size())); 171 mIndexRanges.clear(); 172 } 173 } 174 175 ResetLastUpdateFenceId(); 176 } 177 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; 182 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."); 187 } 188 189 //// 190 191 if (!rawDataLen) return; // With validation successful, nothing else to do. 192 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; 199 200 InvalidateCacheRange(dstByteOffset.value(), dataLen.value()); 201 } 202 203 //// 204 205 const auto& gl = mContext->gl; 206 const ScopedLazyBind lazyBind(gl, target, this); 207 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; 222 } 223 mapping = gl->fMapBufferRange(target, dstByteOffset.value(), 224 dataLen.value(), access); 225 } 226 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); 233 } 234 235 ResetLastUpdateFenceId(); 236 } 237 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; 243 } 244 availLength -= byteOffset; 245 246 if (byteLen > availLength) { 247 mContext->ErrorInvalidValue("Offset+size passes the end of the buffer."); 248 return false; 249 } 250 251 return true; 252 } 253 254 //////////////////////////////////////// 255 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(); 266 } 267 } 268 269 void WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset, 270 uint64_t byteLength) const { 271 MOZ_ASSERT(mIndexCache); 272 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); 284 } 285 286 if (!invalids.empty()) { 287 mContext->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this, 288 uint32_t(invalids.size()), 289 uint32_t(mIndexRanges.size())); 290 291 for (const auto& cur : invalids) { 292 mIndexRanges.erase(cur); 293 } 294 } 295 } 296 297 size_t WebGLBuffer::SizeOfIncludingThis( 298 mozilla::MallocSizeOf mallocSizeOf) const { 299 size_t size = mallocSizeOf(this); 300 if (mIndexCache) { 301 size += mByteLength; 302 } 303 return size; 304 } 305 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; 313 314 auto itr = (const T*)start; 315 const auto end = itr + count; 316 317 for (; itr != end; ++itr) { 318 const auto& val = *itr; 319 if (ignoredVal && val == ignoredVal.value()) continue; 320 321 if (maxVal && val <= maxVal.value()) continue; 322 323 maxVal = Some(val); 324 } 325 326 return maxVal; 327 } 328 329 static const uint32_t kMaxIndexRanges = 256; 330 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(); 335 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()}); 344 } 345 346 const auto& itr = res.first; 347 const auto& didInsert = res.second; 348 349 auto& maxFetchIndex = itr->second; 350 if (didInsert) { 351 const auto& data = mIndexCache.get(); 352 353 const auto start = (const uint8_t*)data + byteOffset; 354 355 Maybe<uint32_t> ignoredVal; 356 if (mContext->IsWebGL2()) { 357 ignoredVal = Some(UINT32_MAX); 358 } 359 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(); 372 } 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); 381 } 382 383 return maxFetchIndex; 384 } 385 386 //// 387 388 bool WebGLBuffer::ValidateCanBindToTarget(GLenum target) { 389 /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1 390 * 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. 397 * 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. 404 */ 405 406 if (mContent == WebGLBuffer::Kind::Undefined) return true; 407 408 switch (target) { 409 case LOCAL_GL_COPY_READ_BUFFER: 410 case LOCAL_GL_COPY_WRITE_BUFFER: 411 return true; 412 413 case LOCAL_GL_ELEMENT_ARRAY_BUFFER: 414 if (mContent == WebGLBuffer::Kind::ElementArray) return true; 415 break; 416 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; 424 425 default: 426 MOZ_CRASH(); 427 } 428 429 const auto dataType = 430 (mContent == WebGLBuffer::Kind::OtherData) ? "other" : "element"; 431 mContext->ErrorInvalidOperation("Buffer already contains %s data.", dataType); 432 return false; 433 } 434 435 void WebGLBuffer::ResetLastUpdateFenceId() const { 436 mLastUpdateFenceId = mContext->mNextFenceId; 437 } 438 439 } // namespace mozilla