Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  DrawTargetWebgl.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "DrawTargetWebglInternal.h"
#include "SourceSurfaceWebgl.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/gfx/AAStroke.h"
#include "mozilla/gfx/Blur.h"
#include "mozilla/gfx/DrawTargetSkia.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/HelpersSkia.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/PathSkia.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/RemoteTextureMap.h"
#include "mozilla/widget/ScreenManager.h"
#include "skia/include/core/SkPixmap.h"
#include "nsContentUtils.h"

#include "GLContext.h"
#include "WebGLContext.h"
#include "WebGLChild.h"
#include "WebGLBuffer.h"
#include "WebGLFramebuffer.h"
#include "WebGLProgram.h"
#include "WebGLShader.h"
#include "WebGLTexture.h"
#include "WebGLVertexArray.h"

#include "gfxPlatform.h"

#ifdef XP_MACOSX
#  include "mozilla/gfx/ScaledFontMac.h"
#endif

namespace mozilla::gfx {

BackingTexture::BackingTexture(const IntSize& aSize, SurfaceFormat aFormat,
                               const RefPtr<WebGLTexture>& aTexture)
    : mSize(aSize), mFormat(aFormat), mTexture(aTexture) {}

#ifdef XP_WIN
// Work around buggy ANGLE/D3D drivers that may copy blocks of pixels outside
// the row length. Extra space is reserved at the end of each row up to stride
// alignment. This does not affect standalone textures.
static const Etagere::AllocatorOptions kR8AllocatorOptions = {16, 1, 1, 0};
#endif

SharedTexture::SharedTexture(const IntSize& aSize, SurfaceFormat aFormat,
                             const RefPtr<WebGLTexture>& aTexture)
    : BackingTexture(aSize, aFormat, aTexture),
      mAtlasAllocator(
#ifdef XP_WIN
          aFormat == SurfaceFormat::A8
              ? Etagere::etagere_atlas_allocator_with_options(
                    aSize.width, aSize.height, &kR8AllocatorOptions)
              :
#endif
              Etagere::etagere_atlas_allocator_new(aSize.width, aSize.height)) {
}

SharedTexture::~SharedTexture() {
  if (mAtlasAllocator) {
    Etagere::etagere_atlas_allocator_delete(mAtlasAllocator);
    mAtlasAllocator = nullptr;
  }
}

SharedTextureHandle::SharedTextureHandle(Etagere::AllocationId aId,
                                         const IntRect& aBounds,
                                         SharedTexture* aTexture)
    : mAllocationId(aId), mBounds(aBounds), mTexture(aTexture) {}

already_AddRefed<SharedTextureHandle> SharedTexture::Allocate(
    const IntSize& aSize) {
  Etagere::Allocation alloc = {{0, 0, 0, 0}, Etagere::INVALID_ALLOCATION_ID};
  if (!mAtlasAllocator ||
      !Etagere::etagere_atlas_allocator_allocate(mAtlasAllocator, aSize.width,
                                                 aSize.height, &alloc) ||
      alloc.id == Etagere::INVALID_ALLOCATION_ID) {
    return nullptr;
  }
  RefPtr<SharedTextureHandle> handle = new SharedTextureHandle(
      alloc.id,
      IntRect(IntPoint(alloc.rectangle.min_x, alloc.rectangle.min_y), aSize),
      this);
  return handle.forget();
}

bool SharedTexture::Free(SharedTextureHandle& aHandle) {
  if (aHandle.mTexture != this) {
    return false;
  }
  if (aHandle.mAllocationId != Etagere::INVALID_ALLOCATION_ID) {
    if (mAtlasAllocator) {
      Etagere::etagere_atlas_allocator_deallocate(mAtlasAllocator,
                                                  aHandle.mAllocationId);
    }
    aHandle.mAllocationId = Etagere::INVALID_ALLOCATION_ID;
  }
  return true;
}

StandaloneTexture::StandaloneTexture(const IntSize& aSize,
                                     SurfaceFormat aFormat,
                                     const RefPtr<WebGLTexture>& aTexture)
    : BackingTexture(aSize, aFormat, aTexture) {}

DrawTargetWebgl::DrawTargetWebgl() = default;

inline void SharedContextWebgl::ClearLastTexture(bool aFullClear) {
  mLastTexture = nullptr;
  if (aFullClear) {
    mLastClipMask = nullptr;
  }
}

// Attempts to clear the snapshot state. If the snapshot is only referenced by
// this target, then it should simply be destroyed. If it is a WebGL surface in
// use by something else, then special cleanup such as reusing the texture or
// copy-on-write may be possible.
void DrawTargetWebgl::ClearSnapshot(bool aCopyOnWrite, bool aNeedHandle) {
  if (!mSnapshot) {
    return;
  }
  mSharedContext->ClearLastTexture();
  RefPtr<SourceSurfaceWebgl> snapshot = mSnapshot.forget();
  if (snapshot->hasOneRef()) {
    return;
  }
  if (aCopyOnWrite) {
    // WebGL snapshots must be notified that the framebuffer contents will be
    // changing so that it can copy the data.
    snapshot->DrawTargetWillChange(aNeedHandle);
  } else {
    // If not copying, then give the backing texture to the surface for reuse.
    snapshot->GiveTexture(
        mSharedContext->WrapSnapshot(GetSize(), GetFormat(), mTex.forget()));
  }
}

DrawTargetWebgl::~DrawTargetWebgl() {
  ClearSnapshot(false);
  if (mSharedContext) {
    // Force any Skia snapshots to copy the shmem before it deallocs.
    if (mSkia) {
      mSkia->DetachAllSnapshots();
    }
    mSharedContext->ClearLastTexture(true);
    mClipMask = nullptr;
    mFramebuffer = nullptr;
    mTex = nullptr;
    mSharedContext->mDrawTargetCount--;
  }
}

SharedContextWebgl::SharedContextWebgl() = default;

SharedContextWebgl::~SharedContextWebgl() {
  // Detect context loss before deletion.
  if (mWebgl) {
    ExitTlsScope();
    mWebgl->ActiveTexture(0);
  }
  if (mWGRPathBuilder) {
    WGR::wgr_builder_release(mWGRPathBuilder);
    mWGRPathBuilder = nullptr;
  }
  ClearAllTextures();
  UnlinkSurfaceTextures();
  UnlinkGlyphCaches();
}

gl::GLContext* SharedContextWebgl::GetGLContext() {
  return mWebgl ? mWebgl->GL() : nullptr;
}

void SharedContextWebgl::EnterTlsScope() {
  if (mTlsScope.isSome()) {
    return;
  }
  if (gl::GLContext* gl = GetGLContext()) {
    mTlsScope = Some(gl->mUseTLSIsCurrent);
    gl::GLContext::InvalidateCurrentContext();
    gl->mUseTLSIsCurrent = true;
  }
}

void SharedContextWebgl::ExitTlsScope() {
  if (mTlsScope.isNothing()) {
    return;
  }
  if (gl::GLContext* gl = GetGLContext()) {
    gl->mUseTLSIsCurrent = mTlsScope.value();
  }
  mTlsScope = Nothing();
}

// Remove any SourceSurface user data associated with this TextureHandle.
inline void SharedContextWebgl::UnlinkSurfaceTexture(
    const RefPtr<TextureHandle>& aHandle) {
  if (RefPtr<SourceSurface> surface = aHandle->GetSurface()) {
    // Ensure any WebGL snapshot textures get unlinked.
    if (surface->GetType() == SurfaceType::WEBGL) {
      static_cast<SourceSurfaceWebgl*>(surface.get())->OnUnlinkTexture(this);
    }
    surface->RemoveUserData(aHandle->IsShadow() ? &mShadowTextureKey
                                                : &mTextureHandleKey);
  }
}

// Unlinks TextureHandles from any SourceSurface user data.
void SharedContextWebgl::UnlinkSurfaceTextures() {
  for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle;
       handle = handle->getNext()) {
    UnlinkSurfaceTexture(handle);
  }
}

// Unlinks GlyphCaches from any ScaledFont user data.
void SharedContextWebgl::UnlinkGlyphCaches() {
  GlyphCache* cache = mGlyphCaches.getFirst();
  while (cache) {
    ScaledFont* font = cache->GetFont();
    // Access the next cache before removing the user data, as it might destroy
    // the cache.
    cache = cache->getNext();
    font->RemoveUserData(&mGlyphCacheKey);
  }
}

void SharedContextWebgl::OnMemoryPressure() { mShouldClearCaches = true; }

void SharedContextWebgl::ClearCaches() {
  OnMemoryPressure();
  ClearCachesIfNecessary();
}

// Clear out the entire list of texture handles from any source.
void SharedContextWebgl::ClearAllTextures() {
  while (!mTextureHandles.isEmpty()) {
    PruneTextureHandle(mTextureHandles.popLast());
    --mNumTextureHandles;
  }
}

// Scan through the shared texture pages looking for any that are empty and
// delete them.
void SharedContextWebgl::ClearEmptyTextureMemory() {
  for (auto pos = mSharedTextures.begin(); pos != mSharedTextures.end();) {
    if (!(*pos)->HasAllocatedHandles()) {
      RefPtr<SharedTexture> shared = *pos;
      size_t usedBytes = shared->UsedBytes();
      mEmptyTextureMemory -= usedBytes;
      mTotalTextureMemory -= usedBytes;
      pos = mSharedTextures.erase(pos);
    } else {
      ++pos;
    }
  }
}

// If there is a request to clear out the caches because of memory pressure,
// then first clear out all the texture handles in the texture cache. If there
// are still empty texture pages being kept around, then clear those too.
void SharedContextWebgl::ClearCachesIfNecessary() {
  if (!mShouldClearCaches.exchange(false)) {
    return;
  }
  mZeroBuffer = nullptr;
  ClearAllTextures();
  if (mEmptyTextureMemory) {
    ClearEmptyTextureMemory();
  }
  ClearLastTexture();
}

// Try to initialize a new WebGL context. Verifies that the requested size does
// not exceed the available texture limits and that shader creation succeeded.
bool DrawTargetWebgl::Init(const IntSize& size, const SurfaceFormat format,
                           const RefPtr<SharedContextWebgl>& aSharedContext) {
  MOZ_ASSERT(format == SurfaceFormat::B8G8R8A8 ||
             format == SurfaceFormat::B8G8R8X8);

  mSize = size;
  mFormat = format;

  if (!aSharedContext || aSharedContext->IsContextLost() ||
      aSharedContext->mDrawTargetCount >=
          StaticPrefs::gfx_canvas_accelerated_max_draw_target_count()) {
    return false;
  }
  mSharedContext = aSharedContext;
  mSharedContext->mDrawTargetCount++;

  if (size_t(std::max(size.width, size.height)) >
      mSharedContext->mMaxTextureSize) {
    return false;
  }

  if (!CreateFramebuffer()) {
    return false;
  }

  size_t byteSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
      mSize, SurfaceFormat::B8G8R8A8);
  if (byteSize == 0) {
    return false;
  }

  size_t shmemSize = mozilla::ipc::SharedMemory::PageAlignedSize(byteSize);
  if (NS_WARN_IF(shmemSize > UINT32_MAX)) {
    MOZ_ASSERT_UNREACHABLE("Buffer too big?");
    return false;
  }

  auto shmem = MakeRefPtr<mozilla::ipc::SharedMemory>();
  if (NS_WARN_IF(!shmem->Create(shmemSize)) ||
      NS_WARN_IF(!shmem->Map(shmemSize))) {
    return false;
  }

  mShmem = std::move(shmem);
  mShmemSize = shmemSize;

  mSkia = new DrawTargetSkia;
  auto stride = layers::ImageDataSerializer::ComputeRGBStride(
      SurfaceFormat::B8G8R8A8, size.width);
  if (!mSkia->Init(reinterpret_cast<uint8_t*>(mShmem->Memory()), size, stride,
                   SurfaceFormat::B8G8R8A8, true)) {
    return false;
  }

  // Allocate an unclipped copy of the DT pointing to its data.
  uint8_t* dtData = nullptr;
  IntSize dtSize;
  int32_t dtStride = 0;
  SurfaceFormat dtFormat = SurfaceFormat::UNKNOWN;
  if (!mSkia->LockBits(&dtData, &dtSize, &dtStride, &dtFormat)) {
    return false;
  }
  mSkiaNoClip = new DrawTargetSkia;
  if (!mSkiaNoClip->Init(dtData, dtSize, dtStride, dtFormat, true)) {
    mSkia->ReleaseBits(dtData);
    return false;
  }
  mSkia->ReleaseBits(dtData);

  SetPermitSubpixelAA(IsOpaque(format));
  return true;
}

// If a non-recoverable error occurred that would stop the canvas from initing.
static Atomic<bool> sContextInitError(false);

already_AddRefed<SharedContextWebgl> SharedContextWebgl::Create() {
  // If context initialization would fail, don't even try to create a context.
  if (sContextInitError) {
    return nullptr;
  }
  RefPtr<SharedContextWebgl> sharedContext = new SharedContextWebgl;
  if (!sharedContext->Initialize()) {
    return nullptr;
  }
  return sharedContext.forget();
}

bool SharedContextWebgl::Initialize() {
  WebGLContextOptions options = {};
  options.alpha = true;
  options.depth = false;
  options.stencil = false;
  options.antialias = false;
  options.preserveDrawingBuffer = true;
  options.failIfMajorPerformanceCaveat = false;

  const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
      "Fallback", RFPTarget::WebGLRenderCapability);
  const auto initDesc = webgl::InitContextDesc{
      .isWebgl2 = true,
      .resistFingerprinting = resistFingerprinting,
      .principalKey = 0,
      .size = {1, 1},
      .options = options,
  };

  webgl::InitContextResult initResult;
  mWebgl = WebGLContext::Create(nullptr, initDesc, &initResult);
  if (!mWebgl) {
    // There was a non-recoverable error when trying to create a host context.
    sContextInitError = true;
    mWebgl = nullptr;
    return false;
  }
  if (mWebgl->IsContextLost()) {
    mWebgl = nullptr;
    return false;
  }

  mMaxTextureSize = initResult.limits.maxTex2dSize;

  if (kIsMacOS) {
    mRasterizationTruncates = initResult.vendor == gl::GLVendor::ATI;
  }

  CachePrefs();

  if (!CreateShaders()) {
    // There was a non-recoverable error when trying to init shaders.
    sContextInitError = true;
    mWebgl = nullptr;
    return false;
  }

  mWGRPathBuilder = WGR::wgr_new_builder();

  return true;
}

inline void SharedContextWebgl::BlendFunc(GLenum aSrcFactor,
                                          GLenum aDstFactor) {
  mWebgl->BlendFuncSeparate({}, aSrcFactor, aDstFactor, aSrcFactor, aDstFactor);
}

void SharedContextWebgl::SetBlendState(CompositionOp aOp,
                                       const Maybe<DeviceColor>& aColor) {
  if (aOp == mLastCompositionOp && mLastBlendColor == aColor) {
    return;
  }
  mLastCompositionOp = aOp;
  mLastBlendColor = aColor;
  // AA is not supported for all composition ops, so switching blend modes may
  // cause a toggle in AA state. Certain ops such as OP_SOURCE require output
  // alpha that is blended separately from AA coverage. This would require two
  // stage blending which can incur a substantial performance penalty, so to
  // work around this currently we just disable AA for those ops.

  // Map the composition op to a WebGL blend mode, if possible.
  bool enabled = true;
  switch (aOp) {
    case CompositionOp::OP_OVER:
      if (aColor) {
        // If a color is supplied, then we blend subpixel text.
        mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, 1.0f);
        BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR);
      } else {
        BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
      }
      break;
    case CompositionOp::OP_ADD:
      BlendFunc(LOCAL_GL_ONE, LOCAL_GL_ONE);
      break;
    case CompositionOp::OP_ATOP:
      BlendFunc(LOCAL_GL_DST_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
      break;
    case CompositionOp::OP_SOURCE:
      if (aColor) {
        // If a color is supplied, then we assume there is clipping or AA. This
        // requires that we still use an over blend func with the clip/AA alpha,
        // while filling the interior with the unaltered color. Normally this
        // would require dual source blending, but we can emulate it with only
        // a blend color.
        mWebgl->BlendColor(aColor->b, aColor->g, aColor->r, aColor->a);
        BlendFunc(LOCAL_GL_CONSTANT_COLOR, LOCAL_GL_ONE_MINUS_SRC_COLOR);
      } else {
        enabled = false;
      }
      break;
    case CompositionOp::OP_CLEAR:
      // Assume the source is an alpha mask for clearing. Be careful to blend in
      // the correct alpha if the target is opaque.
      mWebgl->BlendFuncSeparate(
          {}, LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
          IsOpaque(mCurrentTarget->GetFormat()) ? LOCAL_GL_ONE : LOCAL_GL_ZERO,
          LOCAL_GL_ONE_MINUS_SRC_ALPHA);
      break;
    default:
      enabled = false;
      break;
  }

  mWebgl->SetEnabled(LOCAL_GL_BLEND, {}, enabled);
}

// Ensure the WebGL framebuffer is set to the current target.
bool SharedContextWebgl::SetTarget(DrawTargetWebgl* aDT) {
  if (!mWebgl || mWebgl->IsContextLost()) {
    return false;
  }
  if (aDT != mCurrentTarget) {
    mCurrentTarget = aDT;
    if (aDT) {
      mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, aDT->mFramebuffer);
      mViewportSize = aDT->GetSize();
      mWebgl->Viewport(0, 0, mViewportSize.width, mViewportSize.height);
    }
  }
  return true;
}

// Replace the current clip rect with a new potentially-AA'd clip rect.
void SharedContextWebgl::SetClipRect(const Rect& aClipRect) {
  // Only invalidate the clip rect if it actually changes.
  if (!mClipAARect.IsEqualEdges(aClipRect)) {
    mClipAARect = aClipRect;
    // Store the integer-aligned bounds.
    mClipRect = RoundedOut(aClipRect);
  }
}

bool SharedContextWebgl::SetClipMask(const RefPtr<WebGLTexture>& aTex) {
  if (mLastClipMask != aTex) {
    if (!mWebgl) {
      return false;
    }
    mWebgl->ActiveTexture(1);
    mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, aTex);
    mWebgl->ActiveTexture(0);
    mLastClipMask = aTex;
  }
  return true;
}

bool SharedContextWebgl::SetNoClipMask() {
  if (mNoClipMask) {
    return SetClipMask(mNoClipMask);
  }
  if (!mWebgl) {
    return false;
  }
  mNoClipMask = mWebgl->CreateTexture();
  if (!mNoClipMask) {
    return false;
  }
  mWebgl->ActiveTexture(1);
  mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask);
  static const auto solidMask =
      std::array<const uint8_t, 4>{0xFF, 0xFF, 0xFF, 0xFF};
  mWebgl->TexImage(0, LOCAL_GL_RGBA8, {0, 0, 0},
                   {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE},
                   {LOCAL_GL_TEXTURE_2D,
                    {1, 1, 1},
                    gfxAlphaType::NonPremult,
                    Some(Span{solidMask})});
  InitTexParameters(mNoClipMask, false);
  mWebgl->ActiveTexture(0);
  mLastClipMask = mNoClipMask;
  return true;
}

inline bool DrawTargetWebgl::ClipStack::operator==(
    const DrawTargetWebgl::ClipStack& aOther) const {
  // Verify the transform and bounds match.
  if (!mTransform.FuzzyEquals(aOther.mTransform) ||
      !mRect.IsEqualInterior(aOther.mRect)) {
    return false;
  }
  // Verify the paths match.
  if (!mPath) {
    return !aOther.mPath;
  }
  if (!aOther.mPath ||
      mPath->GetBackendType() != aOther.mPath->GetBackendType()) {
    return false;
  }
  if (mPath->GetBackendType() != BackendType::SKIA) {
    return mPath == aOther.mPath;
  }
  return static_cast<const PathSkia*>(mPath.get())->GetPath() ==
         static_cast<const PathSkia*>(aOther.mPath.get())->GetPath();
}

// If the clip region can't be approximated by a simple clip rect, then we need
// to generate a clip mask that can represent the clip region per-pixel. We
// render to the Skia target temporarily, transparent outside the clip region,
// opaque inside, and upload this to a texture that can be used by the shaders.
bool DrawTargetWebgl::GenerateComplexClipMask() {
  if (!mClipChanged || (mClipMask && mCachedClipStack == mClipStack)) {
    mClipChanged = false;
    // If the clip mask was already generated, use the cached mask and bounds.
    mSharedContext->SetClipMask(mClipMask);
    mSharedContext->SetClipRect(mClipBounds);
    return true;
  }
  if (!mWebglValid) {
    // If the Skia target is currently being used, then we can't render the mask
    // in it.
    return false;
  }
  RefPtr<WebGLContext> webgl = mSharedContext->mWebgl;
  if (!webgl) {
    return false;
  }
  bool init = false;
  if (!mClipMask) {
    mClipMask = webgl->CreateTexture();
    if (!mClipMask) {
      return false;
    }
    init = true;
  }
  // Try to get the bounds of the clip to limit the size of the mask.
  if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(true)) {
    mClipBounds = *clip;
  } else {
    // If we can't get bounds, then just use the entire viewport.
    mClipBounds = GetRect();
  }
  mClipAARect = Rect(mClipBounds);
  // If initializing the clip mask, then allocate the entire texture to ensure
  // all pixels get filled with an empty mask regardless. Otherwise, restrict
  // uploading to only the clip region.
  RefPtr<DrawTargetSkia> dt = new DrawTargetSkia;
  if (!dt->Init(mClipBounds.Size(), SurfaceFormat::A8)) {
    return false;
  }
  // Set the clip region and fill the entire inside of it
  // with opaque white.
  mCachedClipStack.clear();
  for (auto& clipStack : mClipStack) {
    // Record the current state of the clip stack for this mask.
    mCachedClipStack.push_back(clipStack);
    dt->SetTransform(
        Matrix(clipStack.mTransform).PostTranslate(-mClipBounds.TopLeft()));
    if (clipStack.mPath) {
      dt->PushClip(clipStack.mPath);
    } else {
      dt->PushClipRect(clipStack.mRect);
    }
  }
  dt->SetTransform(Matrix::Translation(-mClipBounds.TopLeft()));
  dt->FillRect(Rect(mClipBounds), ColorPattern(DeviceColor(1, 1, 1, 1)));
  // Bind the clip mask for uploading. This is done on texture unit 0 so that
  // we can work around an Windows Intel driver bug. If done on texture unit 1,
  // the driver doesn't notice that the texture contents was modified. Force a
  // re-latch by binding the texture on texture unit 1 only after modification.
  webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mClipMask);
  if (init) {
    mSharedContext->InitTexParameters(mClipMask, false);
  }
  RefPtr<DataSourceSurface> data;
  if (RefPtr<SourceSurface> snapshot = dt->Snapshot()) {
    data = snapshot->GetDataSurface();
  }
  // Finally, upload the texture data and initialize texture storage if
  // necessary.
  if (init && mClipBounds.Size() != mSize) {
    mSharedContext->UploadSurface(nullptr, SurfaceFormat::A8, GetRect(),
                                  IntPoint(), truetrue);
    init = false;
  }
  mSharedContext->UploadSurface(data, SurfaceFormat::A8,
                                IntRect(IntPoint(), mClipBounds.Size()),
                                mClipBounds.TopLeft(), init);
  mSharedContext->ClearLastTexture();
  // Bind the new clip mask to the clip sampler on texture unit 1.
  mSharedContext->SetClipMask(mClipMask);
  mSharedContext->SetClipRect(mClipBounds);
  // We uploaded a surface, just as if we missed the texture cache, so account
  // for that here.
  mProfile.OnCacheMiss();
  return !!data;
}

bool DrawTargetWebgl::SetSimpleClipRect() {
  // Determine whether the clipping rectangle is simple enough to accelerate.
  // Check if there is a device space clip rectangle available from the Skia
  // target.
  if (Maybe<IntRect> clip = mSkia->GetDeviceClipRect(false)) {
    // If the clip is empty, leave the final integer clip rectangle empty to
    // trivially discard the draw request.
    // If the clip rect is larger than the viewport, just set it to the
    // viewport.
    if (!clip->IsEmpty() && clip->Contains(GetRect())) {
      clip = Some(GetRect());
    }
    mSharedContext->SetClipRect(*clip);
    mSharedContext->SetNoClipMask();
    return true;
  }

  // There was no pixel-aligned clip rect available, so check the clip stack to
  // see if there is an AA'd axis-aligned rectangle clip.
  Rect rect(GetRect());
  for (auto& clipStack : mClipStack) {
    // If clip is a path or it has a non-axis-aligned transform, then it is
    // complex.
    if (clipStack.mPath ||
        !clipStack.mTransform.PreservesAxisAlignedRectangles()) {
      return false;
    }
    // Transform the rect and intersect it with the current clip.
    rect =
        clipStack.mTransform.TransformBounds(clipStack.mRect).Intersect(rect);
  }
  mSharedContext->SetClipRect(rect);
  mSharedContext->SetNoClipMask();
  return true;
}

// Installs the Skia clip rectangle, if applicable, onto the shared WebGL
// context as well as sets the WebGL framebuffer to the current target.
bool DrawTargetWebgl::PrepareContext(bool aClipped) {
  if (!aClipped) {
    // If no clipping requested, just set the clip rect to the viewport.
    mSharedContext->SetClipRect(GetRect());
    mSharedContext->SetNoClipMask();
    // Ensure the clip gets reset if clipping is later requested for the target.
    mRefreshClipState = true;
  } else if (mRefreshClipState || !mSharedContext->IsCurrentTarget(this)) {
    // Try to use a simple clip rect if possible. Otherwise, fall back to
    // generating a clip mask texture that can represent complex clip regions.
    if (!SetSimpleClipRect() && !GenerateComplexClipMask()) {
      return false;
    }
    mClipChanged = false;
    mRefreshClipState = false;
  }
  return mSharedContext->SetTarget(this);
}

bool SharedContextWebgl::IsContextLost() const {
  return !mWebgl || mWebgl->IsContextLost();
}

// Signal to CanvasRenderingContext2D when the WebGL context is lost.
bool DrawTargetWebgl::IsValid() const {
  return mSharedContext && !mSharedContext->IsContextLost();
}

bool DrawTargetWebgl::CanCreate(const IntSize& aSize, SurfaceFormat aFormat) {
  if (!gfxVars::UseAcceleratedCanvas2D()) {
    return false;
  }

  if (!Factory::AllowedSurfaceSize(aSize)) {
    return false;
  }

  // The interpretation of the min-size and max-size follows from the old
  // SkiaGL prefs. First just ensure that the context is not unreasonably
  // small.
  static const int32_t kMinDimension = 16;
  if (std::min(aSize.width, aSize.height) < kMinDimension) {
    return false;
  }

  int32_t minSize = StaticPrefs::gfx_canvas_accelerated_min_size();
  if (aSize.width * aSize.height < minSize * minSize) {
    return false;
  }

  // Maximum pref allows 3 different options:
  //  0 means unlimited size,
  //  > 0 means use value as an absolute threshold,
  //  < 0 means use the number of screen pixels as a threshold.
  int32_t maxSize = StaticPrefs::gfx_canvas_accelerated_max_size();
  if (maxSize > 0) {
    if (std::max(aSize.width, aSize.height) > maxSize) {
      return false;
    }
  } else if (maxSize < 0) {
    // Default to historical mobile screen size of 980x480, like FishIEtank.
    // In addition, allow acceleration up to this size even if the screen is
    // smaller. A lot content expects this size to work well. See Bug 999841
    static const int32_t kScreenPixels = 980 * 480;

    if (RefPtr<widget::Screen> screen =
            widget::ScreenManager::GetSingleton().GetPrimaryScreen()) {
      LayoutDeviceIntSize screenSize = screen->GetRect().Size();
      if (aSize.width * aSize.height >
          std::max(screenSize.width * screenSize.height, kScreenPixels)) {
        return false;
      }
    }
  }

  return true;
}

already_AddRefed<DrawTargetWebgl> DrawTargetWebgl::Create(
    const IntSize& aSize, SurfaceFormat aFormat,
    const RefPtr<SharedContextWebgl>& aSharedContext) {
  // Validate the size and format.
  if (!CanCreate(aSize, aFormat)) {
    return nullptr;
  }

  RefPtr<DrawTargetWebgl> dt = new DrawTargetWebgl;
  if (!dt->Init(aSize, aFormat, aSharedContext) || !dt->IsValid()) {
    return nullptr;
  }

  return dt.forget();
}

void* DrawTargetWebgl::GetNativeSurface(NativeSurfaceType aType) {
  switch (aType) {
    case NativeSurfaceType::WEBGL_CONTEXT:
      // If the context is lost, then don't attempt to access it.
      if (mSharedContext->IsContextLost()) {
        return nullptr;
      }
      if (!mWebglValid) {
        FlushFromSkia();
      }
      return mSharedContext->mWebgl.get();
    default:
      return nullptr;
  }
}

// Wrap a WebGL texture holding a snapshot with a texture handle. Note that
// while the texture is still in use as the backing texture of a framebuffer,
// it's texture memory is not currently tracked with other texture handles.
// Once it is finally orphaned and used as a texture handle, it must be added
// to the resource usage totals.
already_AddRefed<TextureHandle> SharedContextWebgl::WrapSnapshot(
    const IntSize& aSize, SurfaceFormat aFormat, RefPtr<WebGLTexture> aTex) {
  // Ensure there is enough space for the texture.
  size_t usedBytes = BackingTexture::UsedBytes(aFormat, aSize);
  PruneTextureMemory(usedBytes, false);
  // Allocate a handle for the texture
  RefPtr<StandaloneTexture> handle =
      new StandaloneTexture(aSize, aFormat, aTex.forget());
  mStandaloneTextures.push_back(handle);
  mTextureHandles.insertFront(handle);
  mTotalTextureMemory += usedBytes;
  mUsedTextureMemory += usedBytes;
  ++mNumTextureHandles;
  return handle.forget();
}

void SharedContextWebgl::SetTexFilter(WebGLTexture* aTex, bool aFilter) {
  mWebgl->TexParameter_base(
      LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
      FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST));
  mWebgl->TexParameter_base(
      LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
      FloatOrInt(aFilter ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST));
}

void SharedContextWebgl::InitTexParameters(WebGLTexture* aTex, bool aFilter) {
  mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
                            FloatOrInt(LOCAL_GL_REPEAT));
  mWebgl->TexParameter_base(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
                            FloatOrInt(LOCAL_GL_REPEAT));
  SetTexFilter(aTex, aFilter);
}

// Copy the contents of the WebGL framebuffer into a WebGL texture.
already_AddRefed<TextureHandle> SharedContextWebgl::CopySnapshot(
    const IntRect& aRect, TextureHandle* aHandle) {
  if (!mWebgl || mWebgl->IsContextLost()) {
    return nullptr;
  }

  // If the target is going away, then we can just directly reuse the
  // framebuffer texture since it will never change.
  RefPtr<WebGLTexture> tex = mWebgl->CreateTexture();
  if (!tex) {
    return nullptr;
  }

  // If copying from a non-DT source, we have to bind a scratch framebuffer for
  // reading.
  if (aHandle) {
    if (!mScratchFramebuffer) {
      mScratchFramebuffer = mWebgl->CreateFramebuffer();
    }
    mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);

    webgl::FbAttachInfo attachInfo;
    attachInfo.tex = aHandle->GetBackingTexture()->GetWebGLTexture();
    mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
                              LOCAL_GL_TEXTURE_2D, attachInfo);
  }

  // Create a texture to hold the copy
  mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, tex);
  mWebgl->TexStorage(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8,
                     {uint32_t(aRect.width), uint32_t(aRect.height), 1});
  InitTexParameters(tex);
  // Copy the framebuffer into the texture
  mWebgl->CopyTexImage(LOCAL_GL_TEXTURE_2D, 0, 0, {0, 0, 0}, {aRect.x, aRect.y},
                       {uint32_t(aRect.width), uint32_t(aRect.height)});
  ClearLastTexture();

  SurfaceFormat format =
      aHandle ? aHandle->GetFormat() : mCurrentTarget->GetFormat();
  already_AddRefed<TextureHandle> result =
      WrapSnapshot(aRect.Size(), format, tex.forget());

  // Restore the actual framebuffer after reading is done.
  if (aHandle && mCurrentTarget) {
    mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
  }

  return result;
}

inline DrawTargetWebgl::AutoRestoreContext::AutoRestoreContext(
    DrawTargetWebgl* aTarget)
    : mTarget(aTarget),
      mClipAARect(aTarget->mSharedContext->mClipAARect),
      mLastClipMask(aTarget->mSharedContext->mLastClipMask) {}

inline DrawTargetWebgl::AutoRestoreContext::~AutoRestoreContext() {
  mTarget->mSharedContext->SetClipRect(mClipAARect);
  if (mLastClipMask) {
    mTarget->mSharedContext->SetClipMask(mLastClipMask);
  }
  mTarget->mRefreshClipState = true;
}

// Utility method to install the target before copying a snapshot.
already_AddRefed<TextureHandle> DrawTargetWebgl::CopySnapshot(
    const IntRect& aRect) {
  AutoRestoreContext restore(this);
  if (!PrepareContext(false)) {
    return nullptr;
  }
  return mSharedContext->CopySnapshot(aRect);
}

bool DrawTargetWebgl::HasDataSnapshot() const {
  return (mSkiaValid && !mSkiaLayer) || (mSnapshot && mSnapshot->HasReadData());
}

bool DrawTargetWebgl::PrepareSkia() {
  if (!mSkiaValid) {
    ReadIntoSkia();
  } else if (mSkiaLayer) {
    FlattenSkia();
  }
  return mSkiaValid;
}

bool DrawTargetWebgl::EnsureDataSnapshot() {
  return HasDataSnapshot() || PrepareSkia();
}

void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); }

// Borrow a snapshot that may be used by another thread for composition. Only
// Skia snapshots are safe to pass around.
already_AddRefed<SourceSurface> DrawTargetWebgl::GetDataSnapshot() {
  PrepareSkia();
  return mSkia->Snapshot(mFormat);
}

already_AddRefed<SourceSurface> DrawTargetWebgl::Snapshot() {
  // If already using the Skia fallback, then just snapshot that.
  if (mSkiaValid) {
    return GetDataSnapshot();
  }

  // There's no valid Skia snapshot, so we need to get one from the WebGL
  // context.
  if (!mSnapshot) {
    // Create a copy-on-write reference to this target.
    mSnapshot = new SourceSurfaceWebgl(this);
  }
  return do_AddRef(mSnapshot);
}

// If we need to provide a snapshot for another DrawTargetWebgl that shares the
// same WebGL context, then it is safe to directly return a snapshot. Otherwise,
// we may be exporting to another thread and require a data snapshot.
already_AddRefed<SourceSurface> DrawTargetWebgl::GetOptimizedSnapshot(
    DrawTarget* aTarget) {
  if (aTarget && aTarget->GetBackendType() == BackendType::WEBGL &&
      static_cast<DrawTargetWebgl*>(aTarget)->mSharedContext ==
          mSharedContext) {
    return Snapshot();
  }
  return GetDataSnapshot();
}

// Read from the WebGL context into a buffer. This handles both swizzling BGRA
// to RGBA and flipping the image.
bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
                                  SurfaceFormat aFormat, const IntRect& aBounds,
                                  TextureHandle* aHandle) {
  MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
             aFormat == SurfaceFormat::B8G8R8X8);

  // If reading into a new texture, we have to bind it to a scratch framebuffer
  // for reading.
  if (aHandle) {
    if (!mScratchFramebuffer) {
      mScratchFramebuffer = mWebgl->CreateFramebuffer();
    }
    mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mScratchFramebuffer);
    webgl::FbAttachInfo attachInfo;
    attachInfo.tex = aHandle->GetBackingTexture()->GetWebGLTexture();
    mWebgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
                              LOCAL_GL_TEXTURE_2D, attachInfo);
  } else if (mCurrentTarget && mCurrentTarget->mIsClear) {
    // If reading from a target that is still clear, then avoid the readback by
    // just clearing the data.
    SkPixmap(MakeSkiaImageInfo(aBounds.Size(), aFormat), aDstData, aDstStride)
        .erase(IsOpaque(aFormat) ? SK_ColorBLACK : SK_ColorTRANSPARENT);
    return true;
  }

  webgl::ReadPixelsDesc desc;
  desc.srcOffset = *ivec2::From(aBounds);
  desc.size = *uvec2::FromSize(aBounds);
  desc.packState.rowLength = aDstStride / 4;
  Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
  mWebgl->ReadPixelsInto(desc, range);

  // Restore the actual framebuffer after reading is done.
  if (aHandle && mCurrentTarget) {
    mWebgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mCurrentTarget->mFramebuffer);
  }

  return true;
}

already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
    TextureHandle* aHandle) {
  // Allocate a data surface, map it, and read from the WebGL context into the
  // surface.
  SurfaceFormat format = SurfaceFormat::UNKNOWN;
  IntRect bounds;
  if (aHandle) {
    format = aHandle->GetFormat();
    bounds = aHandle->GetBounds();
  } else {
    format = mCurrentTarget->GetFormat();
    bounds = mCurrentTarget->GetRect();
  }
  RefPtr<DataSourceSurface> surface =
      Factory::CreateDataSourceSurface(bounds.Size(), format);
  if (!surface) {
    return nullptr;
  }
  DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
  if (!dstMap.IsMapped() || !ReadInto(dstMap.GetData(), dstMap.GetStride(),
                                      format, bounds, aHandle)) {
    return nullptr;
  }
  return surface.forget();
}

// Utility method to install the target before reading a snapshot.
bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
  if (!PrepareContext(false)) {
    return false;
  }

  return mSharedContext->ReadInto(aDstData, aDstStride, GetFormat(), GetRect());
}

// Utility method to install the target before reading a snapshot.
already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
  AutoRestoreContext restore(this);
  if (!PrepareContext(false)) {
    return nullptr;
  }
  mProfile.OnReadback();
  return mSharedContext->ReadSnapshot();
}

already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
  return Snapshot();
}

void DrawTargetWebgl::DetachAllSnapshots() {
  mSkia->DetachAllSnapshots();
  ClearSnapshot();
}

// Prepare the framebuffer for accelerated drawing. Any cached snapshots will
// be invalidated if not detached and copied here. Ensure the WebGL
// framebuffer's contents are updated if still somehow stored in the Skia
// framebuffer.
bool DrawTargetWebgl::MarkChanged() {
  if (mSnapshot) {
    // Try to copy the target into a new texture if possible.
    ClearSnapshot(truetrue);
  }
  if (!mWebglValid && !FlushFromSkia()) {
    return false;
  }
  mSkiaValid = false;
  mIsClear = false;
  return true;
}

void DrawTargetWebgl::MarkSkiaChanged(bool aOverwrite) {
  if (aOverwrite) {
    mSkiaValid = true;
    mSkiaLayer = false;
  } else if (!mSkiaValid) {
    if (ReadIntoSkia()) {
      // Signal that we've hit a complete software fallback.
      mProfile.OnFallback();
    }
  } else if (mSkiaLayer) {
    FlattenSkia();
  }
  mWebglValid = false;
  mIsClear = false;
}

// Whether a given composition operator is associative and thus allows drawing
// into a separate layer that can be later composited back into the WebGL
// context.
static inline bool SupportsLayering(const DrawOptions& aOptions) {
  switch (aOptions.mCompositionOp) {
    case CompositionOp::OP_OVER:
      // Layering is only supported for the default source-over composition op.
      return true;
    default:
      return false;
  }
}

void DrawTargetWebgl::MarkSkiaChanged(const DrawOptions& aOptions) {
  if (SupportsLayering(aOptions)) {
    if (!mSkiaValid) {
      // If the Skia context needs initialization, clear it and enable layering.
      mSkiaValid = true;
      if (mWebglValid) {
        mProfile.OnLayer();
        mSkiaLayer = true;
        mSkiaLayerClear = mIsClear;
        mSkia->DetachAllSnapshots();
        if (mSkiaLayerClear) {
          // Avoid blending later by making sure the layer background is filled
          // with opaque alpha values if necessary.
          mSkiaNoClip->FillRect(Rect(mSkiaNoClip->GetRect()), GetClearPattern(),
                                DrawOptions(1.0f, CompositionOp::OP_SOURCE));
        } else {
          mSkiaNoClip->ClearRect(Rect(mSkiaNoClip->GetRect()));
        }
      }
    }
    // The WebGL context is no longer up-to-date.
    mWebglValid = false;
    mIsClear = false;
  } else {
    // For other composition ops, just overwrite the Skia data.
    MarkSkiaChanged();
  }
}

bool DrawTargetWebgl::LockBits(uint8_t** aData, IntSize* aSize,
                               int32_t* aStride, SurfaceFormat* aFormat,
                               IntPoint* aOrigin) {
  // Can only access pixels if there is valid, flattened Skia data.
  if (mSkiaValid && !mSkiaLayer) {
    MarkSkiaChanged();
    return mSkia->LockBits(aData, aSize, aStride, aFormat, aOrigin);
  }
  return false;
}

void DrawTargetWebgl::ReleaseBits(uint8_t* aData) {
  // Can only access pixels if there is valid, flattened Skia data.
  if (mSkiaValid && !mSkiaLayer) {
    mSkia->ReleaseBits(aData);
  }
}

// Format is x, y, alpha
static const float kRectVertexData[12] = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                                          1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f};

// Orphans the contents of the path vertex buffer. The beginning of the buffer
// always contains data for a simple rectangle draw to avoid needing to switch
// buffers.
void SharedContextWebgl::ResetPathVertexBuffer(bool aChanged) {
  mWebgl->BindBuffer(LOCAL_GL_ARRAY_BUFFER, mPathVertexBuffer.get());
  mWebgl->UninitializedBufferData_SizeOnly(
      LOCAL_GL_ARRAY_BUFFER,
      std::max(size_t(mPathVertexCapacity), sizeof(kRectVertexData)),
      LOCAL_GL_DYNAMIC_DRAW);
  mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, sizeof(kRectVertexData),
                        (const uint8_t*)kRectVertexData);
  mPathVertexOffset = sizeof(kRectVertexData);
  if (aChanged) {
    mWGROutputBuffer.reset(
        mPathVertexCapacity > 0
            ? new (fallible) WGR::OutputVertex[mPathVertexCapacity /
                                               sizeof(WGR::OutputVertex)]
            : nullptr);
  }
}

// Attempts to create all shaders and resources to be used for drawing commands.
// Returns whether or not this succeeded.
bool SharedContextWebgl::CreateShaders() {
  if (!mPathVertexArray) {
    mPathVertexArray = mWebgl->CreateVertexArray();
  }
  if (!mPathVertexBuffer) {
    mPathVertexBuffer = mWebgl->CreateBuffer();
    mWebgl->BindVertexArray(mPathVertexArray.get());
    ResetPathVertexBuffer();
    mWebgl->EnableVertexAttribArray(0);

    webgl::VertAttribPointerDesc attribDesc;
    attribDesc.channels = 3;
    attribDesc.type = LOCAL_GL_FLOAT;
    attribDesc.normalized = false;
    mWebgl->VertexAttribPointer(0, attribDesc);
  }
  if (!mSolidProgram) {
    // AA is computed by using the basis vectors of the transform to determine
    // both the scale and orientation. The scale is then used to extrude the
    // rectangle outward by 1 screen-space pixel to account for the AA region.
    // The distance to the rectangle edges is passed to the fragment shader in
    // an interpolant, biased by 0.5 so it represents the desired coverage. The
    // minimum coverage is then chosen by the fragment shader to use as an AA
    // coverage value to modulate the color.
    auto vsSource =
        "attribute vec3 a_vertex;\n"
        "uniform vec2 u_transform[3];\n"
        "uniform vec2 u_viewport;\n"
        "uniform vec4 u_clipbounds;\n"
        "uniform float u_aa;\n"
        "varying vec2 v_cliptc;\n"
        "varying vec4 v_clipdist;\n"
        "varying vec4 v_dist;\n"
        "varying float v_alpha;\n"
        "void main() {\n"
        " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
        " dot(u_transform[1], u_transform[1]));\n"
        " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
        " scale *= invScale;\n"
        " vec2 extrude = a_vertex.xy +\n"
        " invScale * (2.0 * a_vertex.xy - 1.0);\n"
        " vec2 vertex = u_transform[0] * extrude.x +\n"
        " u_transform[1] * extrude.y +\n"
        " u_transform[2];\n"
        " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
        " v_cliptc = vertex / u_viewport;\n"
        " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
        " u_clipbounds.zw - vertex);\n"
        " float noAA = 1.0 - u_aa;\n"
        " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n"
        " v_alpha = min(a_vertex.z,\n"
        " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n"
        "}\n";
    auto fsSource =
        "precision mediump float;\n"
        "uniform vec4 u_color;\n"
        "uniform sampler2D u_clipmask;\n"
        "varying highp vec2 v_cliptc;\n"
        "varying vec4 v_clipdist;\n"
        "varying vec4 v_dist;\n"
        "varying float v_alpha;\n"
        "void main() {\n"
        " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
        " vec4 dist = min(v_dist, v_clipdist);\n"
        " dist.xy = min(dist.xy, dist.zw);\n"
        " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n"
        " gl_FragColor = clip * aa * u_color;\n"
        "}\n";
    RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
    mWebgl->ShaderSource(*vsId, vsSource);
    mWebgl->CompileShader(*vsId);
    if (!mWebgl->GetCompileResult(*vsId).success) {
      return false;
    }
    RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
    mWebgl->ShaderSource(*fsId, fsSource);
    mWebgl->CompileShader(*fsId);
    if (!mWebgl->GetCompileResult(*fsId).success) {
      return false;
    }
    mSolidProgram = mWebgl->CreateProgram();
    mWebgl->AttachShader(*mSolidProgram, *vsId);
    mWebgl->AttachShader(*mSolidProgram, *fsId);
    mWebgl->BindAttribLocation(*mSolidProgram, 0, "a_vertex");
    mWebgl->LinkProgram(*mSolidProgram);
    if (!mWebgl->GetLinkResult(*mSolidProgram).success) {
      return false;
    }
    mSolidProgramViewport = GetUniformLocation(mSolidProgram, "u_viewport");
    mSolidProgramAA = GetUniformLocation(mSolidProgram, "u_aa");
    mSolidProgramTransform = GetUniformLocation(mSolidProgram, "u_transform");
    mSolidProgramColor = GetUniformLocation(mSolidProgram, "u_color");
    mSolidProgramClipMask = GetUniformLocation(mSolidProgram, "u_clipmask");
    mSolidProgramClipBounds = GetUniformLocation(mSolidProgram, "u_clipbounds");
    if (!mSolidProgramViewport || !mSolidProgramAA || !mSolidProgramTransform ||
        !mSolidProgramColor || !mSolidProgramClipMask ||
        !mSolidProgramClipBounds) {
      return false;
    }
    mWebgl->UseProgram(mSolidProgram);
    UniformData(LOCAL_GL_INT, mSolidProgramClipMask, Array<int32_t, 1>{1});
  }

  if (!mImageProgram) {
    auto vsSource =
        "attribute vec3 a_vertex;\n"
        "uniform vec2 u_viewport;\n"
        "uniform vec4 u_clipbounds;\n"
        "uniform float u_aa;\n"
        "uniform vec2 u_transform[3];\n"
        "uniform vec2 u_texmatrix[3];\n"
        "varying vec2 v_cliptc;\n"
        "varying vec2 v_texcoord;\n"
        "varying vec4 v_clipdist;\n"
        "varying vec4 v_dist;\n"
        "varying float v_alpha;\n"
        "void main() {\n"
        " vec2 scale = vec2(dot(u_transform[0], u_transform[0]),\n"
        " dot(u_transform[1], u_transform[1]));\n"
        " vec2 invScale = u_aa * inversesqrt(scale + 1.0e-6);\n"
        " scale *= invScale;\n"
        " vec2 extrude = a_vertex.xy +\n"
        " invScale * (2.0 * a_vertex.xy - 1.0);\n"
        " vec2 vertex = u_transform[0] * extrude.x +\n"
        " u_transform[1] * extrude.y +\n"
        " u_transform[2];\n"
        " gl_Position = vec4(vertex * 2.0 / u_viewport - 1.0, 0.0, 1.0);\n"
        " v_cliptc = vertex / u_viewport;\n"
        " v_clipdist = vec4(vertex - u_clipbounds.xy,\n"
        " u_clipbounds.zw - vertex);\n"
        " v_texcoord = u_texmatrix[0] * extrude.x +\n"
        " u_texmatrix[1] * extrude.y +\n"
        " u_texmatrix[2];\n"
        " float noAA = 1.0 - u_aa;\n"
        " v_dist = vec4(extrude, 1.0 - extrude) * scale.xyxy + 0.5 + noAA;\n"
        " v_alpha = min(a_vertex.z,\n"
        " min(scale.x, 1.0) * min(scale.y, 1.0) + noAA);\n"
        "}\n";
    auto fsSource =
        "precision mediump float;\n"
        "uniform vec4 u_texbounds;\n"
        "uniform vec4 u_color;\n"
        "uniform float u_swizzle;\n"
        "uniform sampler2D u_sampler;\n"
        "uniform sampler2D u_clipmask;\n"
        "varying highp vec2 v_cliptc;\n"
        "varying highp vec2 v_texcoord;\n"
        "varying vec4 v_clipdist;\n"
        "varying vec4 v_dist;\n"
        "varying float v_alpha;\n"
        "void main() {\n"
        " highp vec2 tc = clamp(v_texcoord, u_texbounds.xy,\n"
        " u_texbounds.zw);\n"
        " vec4 image = texture2D(u_sampler, tc);\n"
        " float clip = texture2D(u_clipmask, v_cliptc).r;\n"
        " vec4 dist = min(v_dist, v_clipdist);\n"
        " dist.xy = min(dist.xy, dist.zw);\n"
        " float aa = clamp(min(dist.x, dist.y), 0.0, v_alpha);\n"
        " gl_FragColor = clip * aa * u_color *\n"
        " mix(image, image.rrrr, u_swizzle);\n"
        "}\n";
    RefPtr<WebGLShader> vsId = mWebgl->CreateShader(LOCAL_GL_VERTEX_SHADER);
    mWebgl->ShaderSource(*vsId, vsSource);
    mWebgl->CompileShader(*vsId);
    if (!mWebgl->GetCompileResult(*vsId).success) {
      return false;
    }
    RefPtr<WebGLShader> fsId = mWebgl->CreateShader(LOCAL_GL_FRAGMENT_SHADER);
    mWebgl->ShaderSource(*fsId, fsSource);
    mWebgl->CompileShader(*fsId);
    if (!mWebgl->GetCompileResult(*fsId).success) {
      return false;
    }
    mImageProgram = mWebgl->CreateProgram();
    mWebgl->AttachShader(*mImageProgram, *vsId);
    mWebgl->AttachShader(*mImageProgram, *fsId);
    mWebgl->BindAttribLocation(*mImageProgram, 0, "a_vertex");
    mWebgl->LinkProgram(*mImageProgram);
    if (!mWebgl->GetLinkResult(*mImageProgram).success) {
      return false;
    }
    mImageProgramViewport = GetUniformLocation(mImageProgram, "u_viewport");
    mImageProgramAA = GetUniformLocation(mImageProgram, "u_aa");
    mImageProgramTransform = GetUniformLocation(mImageProgram, "u_transform");
    mImageProgramTexMatrix = GetUniformLocation(mImageProgram, "u_texmatrix");
    mImageProgramTexBounds = GetUniformLocation(mImageProgram, "u_texbounds");
    mImageProgramSwizzle = GetUniformLocation(mImageProgram, "u_swizzle");
    mImageProgramColor = GetUniformLocation(mImageProgram, "u_color");
    mImageProgramSampler = GetUniformLocation(mImageProgram, "u_sampler");
    mImageProgramClipMask = GetUniformLocation(mImageProgram, "u_clipmask");
    mImageProgramClipBounds = GetUniformLocation(mImageProgram, "u_clipbounds");
    if (!mImageProgramViewport || !mImageProgramAA || !mImageProgramTransform ||
        !mImageProgramTexMatrix || !mImageProgramTexBounds ||
        !mImageProgramSwizzle || !mImageProgramColor || !mImageProgramSampler ||
        !mImageProgramClipMask || !mImageProgramClipBounds) {
      return false;
    }
    mWebgl->UseProgram(mImageProgram);
    UniformData(LOCAL_GL_INT, mImageProgramSampler, Array<int32_t, 1>{0});
    UniformData(LOCAL_GL_INT, mImageProgramClipMask, Array<int32_t, 1>{1});
  }
  return true;
}

void SharedContextWebgl::EnableScissor(const IntRect& aRect) {
  // Only update scissor state if it actually changes.
  if (!mLastScissor.IsEqualEdges(aRect)) {
    mLastScissor = aRect;
    mWebgl->Scissor(aRect.x, aRect.y, aRect.width, aRect.height);
  }
  if (!mScissorEnabled) {
    mScissorEnabled = true;
    mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, true);
  }
}

void SharedContextWebgl::DisableScissor() {
  if (mScissorEnabled) {
    mScissorEnabled = false;
    mWebgl->SetEnabled(LOCAL_GL_SCISSOR_TEST, {}, false);
  }
}

inline ColorPattern DrawTargetWebgl::GetClearPattern() const {
  return ColorPattern(
      DeviceColor(0.0f, 0.0f, 0.0f, IsOpaque(mFormat) ? 1.0f : 0.0f));
}

template <typename R>
inline RectDouble DrawTargetWebgl::TransformDouble(const R& aRect) const {
  return MatrixDouble(mTransform).TransformBounds(WidenToDouble(aRect));
}

// Check if the transformed rect clips to the viewport.
inline Maybe<Rect> DrawTargetWebgl::RectClippedToViewport(
    const RectDouble& aRect) const {
  if (!mTransform.PreservesAxisAlignedRectangles()) {
    return Nothing();
  }

  return Some(NarrowToFloat(aRect.SafeIntersect(RectDouble(GetRect()))));
}

// Ensure that the rect, after transform, is within reasonable precision limits
// such that when transformed and clipped in the shader it will not round bits
// from the mantissa in a way that will diverge in a noticeable way from path
// geometry calculated by the path fallback.
template <typename R>
static inline bool RectInsidePrecisionLimits(const R& aRect) {
  return R(-(1 << 20), -(1 << 20), 2 << 20, 2 << 20).Contains(aRect);
}

void DrawTargetWebgl::ClearRect(const Rect& aRect) {
  if (mIsClear) {
    // No need to clear anything if the entire framebuffer is already clear.
    return;
  }

  RectDouble xformRect = TransformDouble(aRect);
  bool containsViewport = false;
  if (Maybe<Rect> clipped = RectClippedToViewport(xformRect)) {
    // If the rect clips to viewport, just clear the clipped rect
    // to avoid transform issues.
    containsViewport = clipped->Size() == Size(GetSize());
    DrawRect(*clipped, GetClearPattern(),
             DrawOptions(1.0f, CompositionOp::OP_CLEAR), Nothing(), nullptr,
             false);
  } else if (RectInsidePrecisionLimits(xformRect)) {
    // If the rect transform won't stress precision, then just use it.
    DrawRect(aRect, GetClearPattern(),
             DrawOptions(1.0f, CompositionOp::OP_CLEAR));
  } else {
    // Otherwise, using the transform in the shader may lead to inaccuracies, so
    // just fall back.
    MarkSkiaChanged();
    mSkia->ClearRect(aRect);
  }

  // If the clear rectangle encompasses the entire viewport and is not clipped,
  // then mark the target as entirely clear.
  if (containsViewport && mSharedContext->IsCurrentTarget(this) &&
      !mSharedContext->HasClipMask() &&
      mSharedContext->mClipAARect.Contains(Rect(GetRect()))) {
    mIsClear = true;
  }
}

static inline DeviceColor PremultiplyColor(const DeviceColor& aColor,
                                           float aAlpha = 1.0f) {
  float a = aColor.a * aAlpha;
  return DeviceColor(aColor.r * a, aColor.g * a, aColor.b * a, a);
}

// Attempts to create the framebuffer used for drawing and also any relevant
// non-shared resources. Returns whether or not this succeeded.
bool DrawTargetWebgl::CreateFramebuffer() {
  RefPtr<WebGLContext> webgl = mSharedContext->mWebgl;
  if (!mFramebuffer) {
    mFramebuffer = webgl->CreateFramebuffer();
  }
  if (!mTex) {
    mTex = webgl->CreateTexture();
    webgl->BindTexture(LOCAL_GL_TEXTURE_2D, mTex);
    webgl->TexStorage(LOCAL_GL_TEXTURE_2D, 1, LOCAL_GL_RGBA8,
                      {uint32_t(mSize.width), uint32_t(mSize.height), 1});
    mSharedContext->InitTexParameters(mTex);
    webgl->BindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFramebuffer);
    webgl::FbAttachInfo attachInfo;
    attachInfo.tex = mTex;
    webgl->FramebufferAttach(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
                             LOCAL_GL_TEXTURE_2D, attachInfo);
    webgl->Viewport(0, 0, mSize.width, mSize.height);
    mSharedContext->DisableScissor();
    DeviceColor color = PremultiplyColor(GetClearPattern().mColor);
    webgl->ClearColor(color.b, color.g, color.r, color.a);
    webgl->Clear(LOCAL_GL_COLOR_BUFFER_BIT);
    mSharedContext->ClearTarget();
    mSharedContext->ClearLastTexture();
  }
  return true;
}

void DrawTargetWebgl::CopySurface(SourceSurface* aSurface,
                                  const IntRect& aSourceRect,
                                  const IntPoint& aDestination) {
  // Intersect the source and destination rectangles with the viewport bounds.
  IntRect destRect =
      IntRect(aDestination, aSourceRect.Size()).SafeIntersect(GetRect());
  IntRect srcRect = destRect - aDestination + aSourceRect.TopLeft();
  if (srcRect.IsEmpty()) {
    return;
  }

  if (mSkiaValid) {
    if (mSkiaLayer) {
      if (destRect.Contains(GetRect())) {
        // If the the destination would override the entire layer, discard the
        // layer.
        mSkiaLayer = false;
      } else if (!IsOpaque(aSurface->GetFormat())) {
        // If the surface is not opaque, copying it into the layer results in
        // unintended blending rather than a copy to the destination.
        FlattenSkia();
      }
    } else {
      // If there is no layer, copying is safe.
      MarkSkiaChanged();
    }
    mSkia->CopySurface(aSurface, srcRect, destRect.TopLeft());
    return;
  }

  IntRect samplingRect;
  if (!mSharedContext->IsCompatibleSurface(aSurface)) {
    // If this data surface completely overwrites the framebuffer, then just
    // copy it to the Skia target.
    if (destRect.Contains(GetRect())) {
      MarkSkiaChanged(true);
      mSkia->DetachAllSnapshots();
      mSkiaNoClip->CopySurface(aSurface, srcRect, destRect.TopLeft());
      return;
    }

    // CopySurface usually only samples a surface once, so don't cache the
    // entire surface as it is unlikely to be reused. Limit it to the used
    // source rectangle instead.
    IntRect surfaceRect = aSurface->GetRect();
    if (!srcRect.IsEqualEdges(surfaceRect)) {
      samplingRect = srcRect.SafeIntersect(surfaceRect);
    }
  }

  Matrix matrix = Matrix::Translation(destRect.TopLeft() - srcRect.TopLeft());
  SurfacePattern pattern(aSurface, ExtendMode::CLAMP, matrix,
                         SamplingFilter::POINT, samplingRect);
  DrawRect(Rect(destRect), pattern, DrawOptions(1.0f, CompositionOp::OP_SOURCE),
           Nothing(), nullptr, falsefalse);
}

void DrawTargetWebgl::PushClip(const Path* aPath) {
  if (aPath && aPath->GetBackendType() == BackendType::SKIA) {
    // Detect if the path is really just a rect to simplify caching.
    if (Maybe<Rect> rect = aPath->AsRect()) {
      PushClipRect(*rect);
      return;
    }
  }

  mClipChanged = true;
  mRefreshClipState = true;
  mSkia->PushClip(aPath);

  mClipStack.push_back({GetTransform(), Rect(), aPath});
}

void DrawTargetWebgl::PushClipRect(const Rect& aRect) {
  mClipChanged = true;
  mRefreshClipState = true;
  mSkia->PushClipRect(aRect);

  mClipStack.push_back({GetTransform(), aRect, nullptr});
}

void DrawTargetWebgl::PushDeviceSpaceClipRects(const IntRect* aRects,
                                               uint32_t aCount) {
  mClipChanged = true;
  mRefreshClipState = true;
  mSkia->PushDeviceSpaceClipRects(aRects, aCount);

  for (uint32_t i = 0; i < aCount; i++) {
    mClipStack.push_back({Matrix(), Rect(aRects[i]), nullptr});
  }
}

void DrawTargetWebgl::PopClip() {
  mClipChanged = true;
  mRefreshClipState = true;
  mSkia->PopClip();

  mClipStack.pop_back();
}

bool DrawTargetWebgl::RemoveAllClips() {
  if (mClipStack.empty()) {
    return true;
  }
  if (!mSkia->RemoveAllClips()) {
    return false;
  }
  mClipChanged = true;
  mRefreshClipState = true;
  mClipStack.clear();
  return true;
}

bool DrawTargetWebgl::CopyToFallback(DrawTarget* aDT) {
  aDT->RemoveAllClips();
  for (auto& clipStack : mClipStack) {
    aDT->SetTransform(clipStack.mTransform);
    if (clipStack.mPath) {
      aDT->PushClip(clipStack.mPath);
    } else {
      aDT->PushClipRect(clipStack.mRect);
    }
  }
  aDT->SetTransform(GetTransform());

  // An existing data snapshot is required for fallback, as we have to avoid
  // trying to touch the WebGL context, which is assumed to be invalid and not
  // suitable for readback.
  if (HasDataSnapshot()) {
    if (RefPtr<SourceSurface> snapshot = Snapshot()) {
      aDT->CopySurface(snapshot, snapshot->GetRect(), gfx::IntPoint(0, 0));
      return true;
    }
  }
  return false;
}

// Whether a given composition operator can be mapped to a WebGL blend mode.
static inline bool SupportsDrawOptions(const DrawOptions& aOptions) {
  switch (aOptions.mCompositionOp) {
    case CompositionOp::OP_OVER:
    case CompositionOp::OP_ADD:
    case CompositionOp::OP_ATOP:
    case CompositionOp::OP_SOURCE:
    case CompositionOp::OP_CLEAR:
      return true;
    default:
      return false;
  }
}

static inline bool SupportsExtendMode(const SurfacePattern& aPattern) {
  switch (aPattern.mExtendMode) {
    case ExtendMode::CLAMP:
      return true;
    case ExtendMode::REPEAT:
    case ExtendMode::REPEAT_X:
    case ExtendMode::REPEAT_Y:
      if ((!aPattern.mSurface ||
           aPattern.mSurface->GetType() == SurfaceType::WEBGL) &&
          !aPattern.mSamplingRect.IsEmpty()) {
        return false;
      }
      return true;
    default:
      return false;
  }
}

// Whether a pattern can be mapped to an available WebGL shader.
bool SharedContextWebgl::SupportsPattern(const Pattern& aPattern) {
  switch (aPattern.GetType()) {
    case PatternType::COLOR:
      return true;
    case PatternType::SURFACE: {
      auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
      if (!SupportsExtendMode(surfacePattern)) {
        return false;
      }
      if (surfacePattern.mSurface) {
        // If the surface is already uploaded to a texture, then just use it.
        if (IsCompatibleSurface(surfacePattern.mSurface)) {
          return true;
        }

        IntSize size = surfacePattern.mSurface->GetSize();
        // The maximum size a surface can be before triggering a fallback to
        // software. Bound the maximum surface size by the actual texture size
        // limit.
        int32_t maxSize = int32_t(
            std::min(StaticPrefs::gfx_canvas_accelerated_max_surface_size(),
                     mMaxTextureSize));
        // Check if either of the surface dimensions or the sampling rect,
        // if supplied, exceed the maximum.
        if (std::max(size.width, size.height) > maxSize &&
            (surfacePattern.mSamplingRect.IsEmpty() ||
             std::max(surfacePattern.mSamplingRect.width,
                      surfacePattern.mSamplingRect.height) > maxSize)) {
          return false;
        }
      }
      return true;
    }
    default:
      // Patterns other than colors and surfaces are currently not accelerated.
      return false;
  }
}

bool DrawTargetWebgl::DrawRect(const Rect& aRect, const Pattern& aPattern,
                               const DrawOptions& aOptions,
                               Maybe<DeviceColor> aMaskColor,
                               RefPtr<TextureHandle>* aHandle,
                               bool aTransformed, bool aClipped,
                               bool aAccelOnly, bool aForceUpdate,
                               const StrokeOptions* aStrokeOptions) {
  // If there is nothing to draw, then don't draw...
  if (aRect.IsEmpty()) {
    return true;
  }

  // If we're already drawing directly to the WebGL context, then we want to
  // continue to do so. However, if we're drawing into a Skia layer over the
  // WebGL context, then we need to be careful to avoid repeatedly clearing
  // and flushing the layer if we hit a drawing request that can be accelerated
  // in between layered drawing requests, as clearing and flushing the layer
  // can be significantly expensive when repeated. So when a Skia layer is
  // active, if it is possible to continue drawing into the layer, then don't
  // accelerate the drawing request.
  if (mWebglValid || (mSkiaLayer && !mLayerDepth &&
                      (aAccelOnly || !SupportsLayering(aOptions)))) {
    // If we get here, either the WebGL context is being directly drawn to
    // or we are going to flush the Skia layer to it before doing so. The shared
    // context still needs to be claimed and prepared for drawing. If this
    // fails, we just fall back to drawing with Skia below.
    if (PrepareContext(aClipped)) {
      // The shared context is claimed and the framebuffer is now valid, so try
      // accelerated drawing.
      return mSharedContext->DrawRectAccel(
          aRect, aPattern, aOptions, aMaskColor, aHandle, aTransformed,
          aClipped, aAccelOnly, aForceUpdate, aStrokeOptions);
    }
  }

  // Either there is no valid WebGL target to draw into, or we failed to prepare
  // it for drawing. The only thing we can do at this point is fall back to
  // drawing with Skia. If the request explicitly requires accelerated drawing,
  // then draw nothing before returning failure.
  if (!aAccelOnly) {
    DrawRectFallback(aRect, aPattern, aOptions, aMaskColor, aTransformed,
                     aClipped, aStrokeOptions);
  }
  return false;
}

void DrawTargetWebgl::DrawRectFallback(const Rect& aRect,
                                       const Pattern& aPattern,
                                       const DrawOptions& aOptions,
                                       Maybe<DeviceColor> aMaskColor,
                                       bool aTransformed, bool aClipped,
                                       const StrokeOptions* aStrokeOptions) {
  // Invalidate the WebGL target and prepare the Skia target for drawing.
  MarkSkiaChanged(aOptions);

  if (aTransformed) {
    // If transforms are requested, then just translate back to FillRect.
    if (aMaskColor) {
      mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
    } else if (aStrokeOptions) {
      mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
    } else {
      mSkia->FillRect(aRect, aPattern, aOptions);
    }
  } else if (aClipped) {
    // If no transform was requested but clipping is still required, then
    // temporarily reset the transform before translating to FillRect.
    mSkia->SetTransform(Matrix());
    if (aMaskColor) {
      auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
      if (surfacePattern.mSamplingRect.IsEmpty()) {
        mSkia->MaskSurface(ColorPattern(*aMaskColor), surfacePattern.mSurface,
                           aRect.TopLeft(), aOptions);
      } else {
        mSkia->Mask(ColorPattern(*aMaskColor), aPattern, aOptions);
      }
    } else if (aStrokeOptions) {
      mSkia->StrokeRect(aRect, aPattern, *aStrokeOptions, aOptions);
    } else {
      mSkia->FillRect(aRect, aPattern, aOptions);
    }
    mSkia->SetTransform(mTransform);
  } else if (aPattern.GetType() == PatternType::SURFACE) {
    // No transform nor clipping was requested, so it is essentially just a
    // copy.
    auto surfacePattern = static_cast<const SurfacePattern&>(aPattern);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=98 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge