tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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