diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
commit | fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch) | |
tree | 4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /dom/canvas | |
parent | Releasing progress-linux version 124.0.1-1~progress7.99u1. (diff) | |
download | firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas')
43 files changed, 2138 insertions, 620 deletions
diff --git a/dom/canvas/CacheInvalidator.h b/dom/canvas/CacheInvalidator.h index e63f1e4cc2..909aacfc37 100644 --- a/dom/canvas/CacheInvalidator.h +++ b/dom/canvas/CacheInvalidator.h @@ -9,8 +9,7 @@ #include "mozilla/Maybe.h" #include "mozilla/UniquePtr.h" -#include <unordered_map> -#include <unordered_set> +#include "DmdStdContainers.h" #include <vector> // - @@ -25,7 +24,7 @@ class CacheInvalidator { friend class AbstractCache; private: - mutable std::unordered_set<AbstractCache*> mCaches; + mutable webgl::dmd_unordered_set<AbstractCache*> mCaches; public: virtual ~CacheInvalidator() { @@ -38,6 +37,12 @@ class CacheInvalidator { } void InvalidateCaches() const; + + // - + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return mCaches.SizeOfExcludingThis(mso); + } }; // - @@ -122,11 +127,15 @@ class CacheWeakMap final { } }; - using MapT = - std::unordered_map<const KeyT*, UniquePtr<Entry>, DerefHash, DerefEqual>; + using MapT = webgl::dmd_unordered_map<const KeyT*, UniquePtr<Entry>, + DerefHash, DerefEqual>; MapT mMap; public: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return mMap.SizeOfExcludingThis(mso); + } + UniquePtr<Entry> MakeEntry(const KeyT& key, ValueT&& value) { return UniquePtr<Entry>(new Entry(*this, key, std::move(value))); } diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 28579d4b75..529466cc81 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -24,7 +24,7 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/GeneratePlaceholderCanvasData.h" #include "mozilla/dom/VideoFrame.h" -#include "mozilla/gfx/CanvasManagerChild.h" +#include "mozilla/gfx/CanvasShutdownManager.h" #include "nsPresContext.h" #include "nsIInterfaceRequestorUtils.h" @@ -118,6 +118,7 @@ #include "mozilla/dom/SVGImageElement.h" #include "mozilla/dom/TextMetrics.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/Logging.h" #include "nsGlobalWindowInner.h" #include "nsDeviceContext.h" #include "nsFontMetrics.h" @@ -871,41 +872,6 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext) -class CanvasShutdownObserver final : public nsIObserver { - public: - explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas) - : mCanvas(aCanvas) {} - - void OnShutdown() { - if (!mCanvas) { - return; - } - - mCanvas = nullptr; - nsContentUtils::UnregisterShutdownObserver(this); - } - - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - private: - ~CanvasShutdownObserver() = default; - - CanvasRenderingContext2D* mCanvas; -}; - -NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver) - -NS_IMETHODIMP -CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) { - if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - mCanvas->OnShutdown(); - OnShutdown(); - } - - return NS_OK; -} - NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) @@ -1242,14 +1208,8 @@ void CanvasRenderingContext2D::OnShutdown() { } bool CanvasRenderingContext2D::AddShutdownObserver() { - auto* const canvasManager = CanvasManagerChild::Get(); + auto* const canvasManager = CanvasShutdownManager::Get(); if (NS_WARN_IF(!canvasManager)) { - if (NS_IsMainThread()) { - mShutdownObserver = new CanvasShutdownObserver(this); - nsContentUtils::RegisterShutdownObserver(mShutdownObserver); - return true; - } - mHasShutdown = true; return false; } @@ -1259,18 +1219,70 @@ bool CanvasRenderingContext2D::AddShutdownObserver() { } void CanvasRenderingContext2D::RemoveShutdownObserver() { - if (mShutdownObserver) { - mShutdownObserver->OnShutdown(); - mShutdownObserver = nullptr; + auto* const canvasManager = CanvasShutdownManager::MaybeGet(); + if (!canvasManager) { return; } - auto* const canvasManager = CanvasManagerChild::MaybeGet(); - if (!canvasManager) { + canvasManager->RemoveShutdownObserver(this); +} + +void CanvasRenderingContext2D::OnRemoteCanvasLost() { + // We only lose context / data if we are using remote canvas, which is only + // for accelerated targets. + if (!mBufferProvider || !mBufferProvider->IsAccelerated() || mIsContextLost) { return; } - canvasManager->RemoveShutdownObserver(this); + // 2. Set context's context lost to true. + mIsContextLost = mAllowContextRestore = true; + + // 3. Reset the rendering context to its default state given context. + ClearTarget(); + + // We dispatch because it isn't safe to call into the script event handlers, + // and we don't want to mutate our state in CanvasShutdownManager. + NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( + "CanvasRenderingContext2D::OnRemoteCanvasLost", [self = RefPtr{this}] { + // 4. Let shouldRestore be the result of firing an event named + // contextlost at canvas, with the cancelable attribute initialized to + // true. + self->mAllowContextRestore = self->DispatchEvent( + u"contextlost"_ns, CanBubble::eNo, Cancelable::eYes); + })); +} + +void CanvasRenderingContext2D::OnRemoteCanvasRestored() { + // We never lost our context if it was not a remote canvas, nor can we restore + // if we have already shutdown. + if (mHasShutdown || !mIsContextLost || !mAllowContextRestore) { + return; + } + + // We dispatch because it isn't safe to call into the script event handlers, + // and we don't want to mutate our state in CanvasShutdownManager. + NS_DispatchToCurrentThread(NS_NewCancelableRunnableFunction( + "CanvasRenderingContext2D::OnRemoteCanvasRestored", + [self = RefPtr{this}] { + // 5. If shouldRestore is false, then abort these steps. + if (!self->mHasShutdown && self->mIsContextLost && + self->mAllowContextRestore) { + // 7. Set context's context lost to false. + self->mIsContextLost = false; + + // 6. Attempt to restore context by creating a backing storage using + // context's attributes and associating them with context. If this + // fails, then abort these steps. + if (!self->EnsureTarget()) { + self->mIsContextLost = true; + return; + } + + // 8. Fire an event named contextrestored at canvas. + self->DispatchEvent(u"contextrestored"_ns, CanBubble::eNo, + Cancelable::eNo); + } + })); } void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr, @@ -1489,23 +1501,47 @@ bool CanvasRenderingContext2D::BorrowTarget(const IntRect& aPersistedRect, return true; } -bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, +bool CanvasRenderingContext2D::EnsureTarget(ErrorResult& aError, + const gfx::Rect* aCoveredRect, bool aWillClear) { if (AlreadyShutDown()) { gfxCriticalNoteOnce << "Attempt to render into a Canvas2d after shutdown."; SetErrorState(); + aError.ThrowInvalidStateError( + "Cannot use canvas after shutdown initiated."); + return false; + } + + // The spec doesn't say what to do in this case, but Chrome silently fails + // without throwing an error. We should at least throw if the canvas is + // permanently disabled. + if (NS_WARN_IF(mIsContextLost)) { + if (!mAllowContextRestore) { + aError.ThrowInvalidStateError( + "Cannot use canvas as context is lost forever."); + } return false; } if (mTarget) { - return mTarget != sErrorTarget.get(); + if (mTarget == sErrorTarget.get()) { + aError.ThrowInvalidStateError("Canvas is already in error state."); + return false; + } + return true; } // Check that the dimensions are sane if (mWidth > StaticPrefs::gfx_canvas_max_size() || - mHeight > StaticPrefs::gfx_canvas_max_size() || mWidth < 0 || - mHeight < 0) { + mHeight > StaticPrefs::gfx_canvas_max_size()) { + SetErrorState(); + aError.ThrowInvalidStateError("Canvas exceeds max size."); + return false; + } + + if (mWidth < 0 || mHeight < 0) { SetErrorState(); + aError.ThrowInvalidStateError("Canvas has invalid size."); return false; } @@ -1547,7 +1583,7 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, if (!TryAcceleratedTarget(newTarget, newProvider) && !TrySharedTarget(newTarget, newProvider) && - !TryBasicTarget(newTarget, newProvider)) { + !TryBasicTarget(newTarget, newProvider, aError)) { gfxCriticalError( CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize()))) << "Failed borrow shared and basic targets."; @@ -1684,15 +1720,27 @@ bool CanvasRenderingContext2D::TryAcceleratedTarget( return false; } - if (!mCanvasElement) { - return false; - } - WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); - if (!renderer) { - return false; + if (mCanvasElement) { + MOZ_ASSERT(NS_IsMainThread()); + + WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); + if (NS_WARN_IF(!renderer)) { + return false; + } + + aOutProvider = PersistentBufferProviderAccelerated::Create( + GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor()); + } else if (mOffscreenCanvas && + StaticPrefs::gfx_canvas_remote_allow_offscreen()) { + RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); + if (NS_WARN_IF(!imageBridge)) { + return false; + } + + aOutProvider = PersistentBufferProviderAccelerated::Create( + GetSize(), GetSurfaceFormat(), imageBridge); } - aOutProvider = PersistentBufferProviderAccelerated::Create( - GetSize(), GetSurfaceFormat(), renderer->AsKnowsCompositor()); + if (!aOutProvider) { return false; } @@ -1716,8 +1764,10 @@ bool CanvasRenderingContext2D::TrySharedTarget( } if (mCanvasElement) { + MOZ_ASSERT(NS_IsMainThread()); + WindowRenderer* renderer = WindowRendererFromCanvasElement(mCanvasElement); - if (!renderer) { + if (NS_WARN_IF(!renderer)) { return false; } @@ -1735,7 +1785,7 @@ bool CanvasRenderingContext2D::TrySharedTarget( return false; } - aOutProvider = layers::PersistentBufferProviderShared::Create( + aOutProvider = PersistentBufferProviderShared::Create( GetSize(), GetSurfaceFormat(), imageBridge, !mAllowAcceleration || GetEffectiveWillReadFrequently(), mOffscreenCanvas->GetWindowID()); @@ -1755,10 +1805,12 @@ bool CanvasRenderingContext2D::TrySharedTarget( bool CanvasRenderingContext2D::TryBasicTarget( RefPtr<gfx::DrawTarget>& aOutDT, - RefPtr<layers::PersistentBufferProvider>& aOutProvider) { + RefPtr<layers::PersistentBufferProvider>& aOutProvider, + ErrorResult& aError) { aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget( GetSize(), GetSurfaceFormat()); if (!aOutDT) { + aError.ThrowInvalidStateError("Canvas could not create basic draw target."); return false; } @@ -1767,6 +1819,7 @@ bool CanvasRenderingContext2D::TryBasicTarget( if (!aOutDT->IsValid()) { aOutDT = nullptr; + aError.ThrowInvalidStateError("Canvas could not init basic draw target."); return false; } @@ -2042,7 +2095,7 @@ CanvasRenderingContext2D::GetOptimizedSnapshot(DrawTarget* aTarget, // already exists, otherwise we get performance issues. See bug 1567054. if (!EnsureTarget()) { MOZ_ASSERT( - mTarget == sErrorTarget.get(), + mTarget == sErrorTarget.get() || mIsContextLost, "On EnsureTarget failure mTarget should be set to sErrorTarget."); // In rare circumstances we may have failed to create an error target. return mTarget ? mTarget->Snapshot() : nullptr; @@ -2110,36 +2163,36 @@ void CanvasRenderingContext2D::Restore() { void CanvasRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = mTarget->GetTransform(); newMatrix.PreScale(aX, aY); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform(); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::Translate(double aX, double aY, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix = mTarget->GetTransform(); newMatrix.PreTranslate(aX, aY); SetTransformInternal(newMatrix); @@ -2148,12 +2201,12 @@ void CanvasRenderingContext2D::Translate(double aX, double aY, void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); newMatrix *= mTarget->GetTransform(); SetTransformInternal(newMatrix); @@ -2161,11 +2214,12 @@ void CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, already_AddRefed<DOMMatrix> CanvasRenderingContext2D::GetTransform( ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return nullptr; } + + MOZ_ASSERT(IsTargetValid()); + RefPtr<DOMMatrix> matrix = new DOMMatrix(GetParentObject(), mTarget->GetTransform()); return matrix.forget(); @@ -2175,24 +2229,24 @@ void CanvasRenderingContext2D::SetTransform(double aM11, double aM12, double aM21, double aM22, double aDx, double aDy, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); SetTransformInternal(newMatrix); } void CanvasRenderingContext2D::SetTransform(const DOMMatrix2DInit& aInit, ErrorResult& aError) { - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + RefPtr<DOMMatrixReadOnly> matrix = DOMMatrixReadOnly::FromMatrix(GetParentObject(), aInit, aError); if (!aError.Failed()) { @@ -2420,11 +2474,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern( } else { // Special case for ImageBitmap ImageBitmap& imgBitmap = aSource.GetAsImageBitmap(); - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + if (!EnsureTarget(aError)) { return nullptr; } + + MOZ_ASSERT(IsTargetValid()); + RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget); if (!srcSurf) { aError.ThrowInvalidStateError( @@ -2441,12 +2496,12 @@ already_AddRefed<CanvasPattern> CanvasRenderingContext2D::CreatePattern( return pat.forget(); } - EnsureTarget(); - if (!IsTargetValid()) { - aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + if (!EnsureTarget(aError)) { return nullptr; } + MOZ_ASSERT(IsTargetValid()); + // The canvas spec says that createPattern should use the first frame // of animated images auto flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | @@ -4883,12 +4938,12 @@ UniquePtr<TextMetrics> CanvasRenderingContext2D::DrawOrMeasureText( processor.mPt.x *= processor.mAppUnitsPerDevPixel; processor.mPt.y *= processor.mAppUnitsPerDevPixel; - EnsureTarget(); - if (!IsTargetValid()) { - aError = NS_ERROR_FAILURE; + if (!EnsureTarget(aError)) { return nullptr; } + MOZ_ASSERT(IsTargetValid()); + Matrix oldTransform = mTarget->GetTransform(); bool restoreTransform = false; // if text is over aMaxWidth, then scale the text horizontally such that its @@ -5329,11 +5384,12 @@ void CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage, OffscreenCanvas* offscreenCanvas = nullptr; VideoFrame* videoFrame = nullptr; - EnsureTarget(); - if (!IsTargetValid()) { + if (!EnsureTarget(aError)) { return; } + MOZ_ASSERT(IsTargetValid()); + if (aImage.IsHTMLCanvasElement()) { HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement(); element = canvas; @@ -5776,11 +5832,12 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, GlobalAlpha() == 1.0f && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); const gfx::Rect drawRect(aX, aY, aW, aH); - EnsureTarget(discardContent ? &drawRect : nullptr); - if (!IsTargetValid()) { + if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { return; } + MOZ_ASSERT(IsTargetValid()); + RefPtr<nsPresContext> presContext; nsIDocShell* docshell = aWindow.GetDocShell(); if (docshell) { @@ -5882,7 +5939,11 @@ void CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, &thebes.ref()); // If this canvas was contained in the drawn window, the pre-transaction // callback may have returned its DT. If so, we must reacquire it here. - EnsureTarget(discardContent ? &drawRect : nullptr); + if (!EnsureTarget(aError, discardContent ? &drawRect : nullptr)) { + return; + } + + MOZ_ASSERT(IsTargetValid()); if (drawDT) { RefPtr<SourceSurface> snapshot = drawDT->Snapshot(); @@ -6222,12 +6283,12 @@ void CanvasRenderingContext2D::PutImageData_explicit( // composition operator must not affect the getImageData() and // putImageData() methods. const gfx::Rect putRect(dirtyRect); - EnsureTarget(&putRect); - - if (!IsTargetValid()) { - return aRv.Throw(NS_ERROR_FAILURE); + if (!EnsureTarget(aRv, &putRect)) { + return; } + MOZ_ASSERT(IsTargetValid()); + DataSourceSurface::MappedSurface map; uint8_t* dstData; IntSize dstSize; diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 3376e1033c..75a57ab14f 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -570,6 +570,10 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, void OnShutdown(); + bool IsContextLost() const { return mIsContextLost; } + void OnRemoteCanvasLost(); + void OnRemoteCanvasRestored(); + /** * Update CurrentState().filter with the filter description for * CurrentState().filterChain. @@ -699,8 +703,16 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, * * Returns true on success. */ - bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr, + bool EnsureTarget(ErrorResult& aError, + const gfx::Rect* aCoveredRect = nullptr, bool aWillClear = false); + + bool EnsureTarget(const gfx::Rect* aCoveredRect = nullptr, + bool aWillClear = false) { + IgnoredErrorResult error; + return EnsureTarget(error, aCoveredRect, aWillClear); + } + // Attempt to borrow a new target from an existing buffer provider. bool BorrowTarget(const gfx::IntRect& aPersistedRect, bool aNeedsClear); @@ -714,7 +726,8 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, RefPtr<layers::PersistentBufferProvider>& aOutProvider); bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT, - RefPtr<layers::PersistentBufferProvider>& aOutProvider); + RefPtr<layers::PersistentBufferProvider>& aOutProvider, + ErrorResult& aError); void RegisterAllocation(); @@ -753,7 +766,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, * Check if the target is valid after calling EnsureTarget. */ bool IsTargetValid() const { - return !!mTarget && mTarget != sErrorTarget.get(); + return !!mTarget && mTarget != sErrorTarget.get() && !mIsContextLost; } /** @@ -836,8 +849,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, bool mWillReadFrequently = false; // Whether or not we have already shutdown. bool mHasShutdown = false; + // Whether or not remote canvas is currently unavailable. + bool mIsContextLost = false; + // Whether or not we can restore the context after restoration. + bool mAllowContextRestore = true; - RefPtr<CanvasShutdownObserver> mShutdownObserver; bool AddShutdownObserver(); void RemoveShutdownObserver(); bool AlreadyShutDown() const { return mHasShutdown; } @@ -1008,9 +1024,11 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, RefPtr<nsAtom> fontLanguage; nsFont fontFont; - EnumeratedArray<Style, Style::MAX, RefPtr<CanvasGradient>> gradientStyles; - EnumeratedArray<Style, Style::MAX, RefPtr<CanvasPattern>> patternStyles; - EnumeratedArray<Style, Style::MAX, nscolor> colorStyles; + EnumeratedArray<Style, RefPtr<CanvasGradient>, size_t(Style::MAX)> + gradientStyles; + EnumeratedArray<Style, RefPtr<CanvasPattern>, size_t(Style::MAX)> + patternStyles; + EnumeratedArray<Style, nscolor, size_t(Style::MAX)> colorStyles; nsCString font; CanvasTextAlign textAlign = CanvasTextAlign::Start; diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp index bde265f9a0..1614d2ead2 100644 --- a/dom/canvas/ClientWebGLContext.cpp +++ b/dom/canvas/ClientWebGLContext.cpp @@ -847,7 +847,12 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) { ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability); const auto principalKey = GetPrincipalHashValue(); const auto initDesc = webgl::InitContextDesc{ - mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey}; + .isWebgl2 = mIsWebGL2, + .resistFingerprinting = resistFingerprinting, + .principalKey = principalKey, + .size = requestedSize, + .options = options, + }; // - @@ -1264,28 +1269,13 @@ RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() { MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride); const auto desc = webgl::ReadPixelsDesc{{0, 0}, size}; - const auto range = Range<uint8_t>(map.GetData(), stride * size.y); - if (!DoReadPixels(desc, range)) return nullptr; - - const auto begin = range.begin().get(); + const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y); + if (!DoReadPixels(desc, pixels)) return nullptr; - std::vector<uint8_t> temp; - temp.resize(stride); - for (const auto i : IntegerRange(size.y / 2)) { - const auto top = begin + stride * i; - const auto bottom = begin + stride * (size.y - 1 - i); - memcpy(temp.data(), top, stride); - memcpy(top, bottom, stride); - gfxUtils::ConvertBGRAtoRGBA(top, stride); - - memcpy(bottom, temp.data(), stride); - gfxUtils::ConvertBGRAtoRGBA(bottom, stride); - } - - if (size.y % 2) { - const auto middle = begin + stride * (size.y / 2); - gfxUtils::ConvertBGRAtoRGBA(middle, stride); - } + // RGBA->BGRA and flip-y. + MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData( + pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(), + stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y})); } return surf; @@ -3419,7 +3409,7 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset, const auto& child = notLost->outOfProcess; child->FlushPendingCmds(); mozilla::ipc::Shmem rawShmem; - if (!child->SendGetBufferSubData(target, srcByteOffset, destView->length(), + if (!child->SendGetBufferSubData(target, srcByteOffset, destView->size(), &rawShmem)) { return; } @@ -3429,14 +3419,13 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset, return; } - const auto shmemView = shmem.ByteRange(); - MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView->length()); + const auto shmemView = Span{shmem.ByteRange()}; + MOZ_RELEASE_ASSERT(shmemView.size() == 1 + destView->size()); - const auto ok = bool(*(shmemView.begin().get())); - const auto srcView = - Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()}; + const auto ok = bool(shmemView[0]); + const auto srcView = shmemView.subspan(1); if (ok) { - Memcpy(destView->begin(), srcView.begin(), srcView.length()); + Memcpy(&*destView, srcView); } }); } @@ -3463,8 +3452,8 @@ void ClientWebGLContext::BufferData( if (!ValidateNonNull("src", maybeSrc)) return; const auto& src = maybeSrc.Value(); - src.ProcessFixedData([&](const Span<uint8_t>& aData) { - Run<RPROC(BufferData)>(target, RawBuffer<>(aData), usage); + src.ProcessFixedData([&](const Span<const uint8_t>& aData) { + Run<RPROC(BufferData)>(target, aData, usage); }); } @@ -3481,7 +3470,7 @@ void ClientWebGLContext::BufferData(GLenum target, if (!range) { return; } - Run<RPROC(BufferData)>(target, RawBuffer<>(*range), usage); + Run<RPROC(BufferData)>(target, *range, usage); }); } @@ -3491,8 +3480,8 @@ void ClientWebGLContext::BufferSubData(GLenum target, WebGLsizeiptr dstByteOffset, const dom::ArrayBuffer& src) { const FuncScope funcScope(*this, "bufferSubData"); - src.ProcessFixedData([&](const Span<uint8_t>& aData) { - Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(aData), + src.ProcessFixedData([&](const Span<const uint8_t>& aData) { + Run<RPROC(BufferSubData)>(target, dstByteOffset, aData, /* unsynchronized */ false); }); } @@ -3511,7 +3500,7 @@ void ClientWebGLContext::BufferSubData(GLenum target, if (!range) { return; } - Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(*range), + Run<RPROC(BufferSubData)>(target, dstByteOffset, *range, /* unsynchronized */ false); }); } @@ -3811,9 +3800,7 @@ void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, void ClientWebGLContext::InvalidateFramebuffer( GLenum target, const dom::Sequence<GLenum>& attachments, ErrorResult& unused) { - const auto range = MakeRange(attachments); - const auto& buffer = RawBufferView(range); - Run<RPROC(InvalidateFramebuffer)>(target, buffer); + Run<RPROC(InvalidateFramebuffer)>(target, Span{attachments}); // Never invalidate the backbuffer, so never needs AfterDrawCall. } @@ -3821,9 +3808,8 @@ void ClientWebGLContext::InvalidateFramebuffer( void ClientWebGLContext::InvalidateSubFramebuffer( GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height, ErrorResult& unused) { - const auto range = MakeRange(attachments); - const auto& buffer = RawBufferView(range); - Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height); + Run<RPROC(InvalidateSubFramebuffer)>(target, Span{attachments}, x, y, width, + height); // Never invalidate the backbuffer, so never needs AfterDrawCall. } @@ -4102,13 +4088,11 @@ Range<T> SubRange(const Range<T>& full, const size_t offset, return Range<T>{newBegin, newBegin + length}; } -Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, - size_t bytesPerElem, - GLuint elemOffset, - GLuint elemCountOverride) { - const auto byteRange = Range(data); // In bytes. - - auto elemCount = byteRange.length() / bytesPerElem; +Maybe<Span<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, + size_t bytesPerElem, + GLuint elemOffset, + GLuint elemCountOverride) { + auto elemCount = data.size() / bytesPerElem; if (elemOffset > elemCount) return {}; elemCount -= elemOffset; @@ -4116,9 +4100,8 @@ Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data, if (elemCountOverride > elemCount) return {}; elemCount = elemCountOverride; } - const auto subrange = - SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem); - return Some(subrange); + return Some( + data.subspan(elemOffset * bytesPerElem, elemCount * bytesPerElem)); } // - @@ -4165,7 +4148,8 @@ void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) { CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) * unpack.metrics.totalRows; if (bytesUpperBound.isValid()) { - cpuData->Shrink(bytesUpperBound.value()); + auto& span = *cpuData; + span = span.subspan(0, std::min(span.size(), bytesUpperBound.value())); } } } @@ -4241,7 +4225,7 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget, return Some(webgl::TexUnpackBlobDesc{imageTarget, size.value(), gfxAlphaType::NonPremult, - Some(RawBuffer<>{*range}), + Some(*range), {}}); }); } @@ -4378,12 +4362,9 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget, const auto& contextInfo = mNotLost->info; const auto fallbackReason = [&]() -> Maybe<std::string> { - if (!respecFormat) { - return Some(std::string{ - "Fast uploads not supported for TexSubImage. Use TexImage."}); - } - auto fallbackReason = BlitPreventReason( - level, offset, respecFormat, pi, *desc, contextInfo.isRgb8Renderable); + auto fallbackReason = + BlitPreventReason(level, offset, respecFormat, pi, *desc, + contextInfo.optionalRenderableFormatBits); if (fallbackReason) return fallbackReason; const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType]; @@ -4588,7 +4569,7 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims, RunWithGCData<RPROC(CompressedTexImage)>( std::move(aNoGC), sub, imageTarget, static_cast<uint32_t>(level), - format, CastUvec3(offset), CastUvec3(isize), RawBuffer<>{*range}, + format, CastUvec3(offset), CastUvec3(isize), *range, static_cast<uint32_t>(pboImageSize), Maybe<uint64_t>()); return; }); @@ -4603,8 +4584,8 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims, Run<RPROC(CompressedTexImage)>( sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset), - CastUvec3(isize), RawBuffer<>(), static_cast<uint32_t>(pboImageSize), - Some(*src.mPboOffset)); + CastUvec3(isize), Span<const uint8_t>{}, + static_cast<uint32_t>(pboImageSize), Some(*src.mPboOffset)); } void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget, @@ -4876,9 +4857,8 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType, const auto begin = reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) + elemOffset; - const auto range = Range{begin, availCount}; - RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, - RawBuffer{range}); + const auto range = Span{begin, availCount}; + RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, range); } // - @@ -5095,7 +5075,7 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, } bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, - const Range<uint8_t> dest) const { + const Span<uint8_t> dest) const { const auto notLost = mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF. if (!notLost) return false; @@ -5107,7 +5087,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, const auto& child = notLost->outOfProcess; child->FlushPendingCmds(); webgl::ReadPixelsResultIpc res = {}; - if (!child->SendReadPixels(desc, dest.length(), &res)) { + if (!child->SendReadPixels(desc, dest.size(), &res)) { res = {}; } if (!res.byteStride || !res.shmem) return false; @@ -5119,7 +5099,7 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, return false; } - const auto& shmemBytes = shmem.ByteRange(); + const auto& shmemBytes = Span{shmem.ByteRange()}; const auto pii = webgl::PackingInfoInfo::For(desc.pi); if (!pii) { @@ -5136,19 +5116,13 @@ bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc, const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width); const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp; - auto srcItr = shmemBytes.begin() + byteOffset; - auto destItr = dest.begin() + byteOffset; + const auto srcSubrect = shmemBytes.subspan(byteOffset); + const auto destSubrect = dest.subspan(byteOffset); for (const auto i : IntegerRange(subrect.height)) { - if (i) { - // Don't trigger an assert on the last loop by pushing a RangedPtr past - // its bounds. - srcItr += byteStride; - destItr += byteStride; - MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end()); - MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end()); - } - Memcpy(destItr, srcItr, xByteSize); + const auto srcRow = srcSubrect.subspan(i * byteStride, xByteSize); + const auto destRow = destSubrect.subspan(i * byteStride, xByteSize); + Memcpy(&destRow, srcRow); } return true; @@ -5858,7 +5832,7 @@ void ClientWebGLContext::AttachShader(WebGLProgramJS& prog, void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog, const GLuint location, const nsAString& name) const { - const FuncScope funcScope(*this, "detachShader"); + const FuncScope funcScope(*this, "bindAttribLocation"); if (IsContextLost()) return; if (!prog.ValidateUsable(*this, "program")) return; @@ -6572,7 +6546,7 @@ const webgl::LinkResult& ClientWebGLContext::GetLinkResult( // --------------------------- -Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( +Maybe<Span<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( const Span<uint8_t>& bytes, size_t elemSize, GLuint elemOffset, GLuint elemCountOverride, const GLenum errorEnum) const { size_t elemCount = bytes.Length() / elemSize; @@ -6590,8 +6564,7 @@ Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView( elemCount = elemCountOverride; } - return Some(Range<uint8_t>( - bytes.Subspan(elemOffset * elemSize, elemCount * elemSize))); + return Some(bytes.Subspan(elemOffset * elemSize, elemCount * elemSize)); } // --------------------------- diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h index 47e03d29c5..3c011a3027 100644 --- a/dom/canvas/ClientWebGLContext.h +++ b/dom/canvas/ClientWebGLContext.h @@ -929,11 +929,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal, void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const; public: - Maybe<Range<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes, - size_t elemSize, - GLuint elemOffset, - GLuint elemCountOverride, - const GLenum errorEnum) const; + Maybe<Span<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes, + size_t elemSize, + GLuint elemOffset, + GLuint elemCountOverride, + const GLenum errorEnum) const; protected: template <typename T> @@ -1087,7 +1087,7 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal, private: RefPtr<gfx::DataSourceSurface> BackBufferSnapshot(); [[nodiscard]] bool DoReadPixels(const webgl::ReadPixelsDesc&, - Range<uint8_t>) const; + Span<uint8_t>) const; uvec2 DrawingBufferSize(); // - diff --git a/dom/canvas/DmdStdContainers.h b/dom/canvas/DmdStdContainers.h new file mode 100644 index 0000000000..2975abc580 --- /dev/null +++ b/dom/canvas/DmdStdContainers.h @@ -0,0 +1,100 @@ +/* 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/. */ + +#ifndef dom_canvas_DmdStdContainers_h +#define dom_canvas_DmdStdContainers_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/layers/BuildConstants.h" +#include <unordered_map> +#include <unordered_set> + +namespace mozilla::webgl { + +// - + +template <class T> +class dmd_allocator { + public: + using value_type = T; + + private: + size_t mMallocSize = 0; + + public: + dmd_allocator() = default; + + // - + + template <class U> + friend class dmd_allocator; + + template <class U> + explicit dmd_allocator(const dmd_allocator<U>& rhs) { + if constexpr (kIsDmd) { + mMallocSize = rhs.mMallocSize; + } + } + + // - + + value_type* allocate(const size_t n) { + const auto p = std::allocator<value_type>{}.allocate(n); + if constexpr (kIsDmd) { + mMallocSize += moz_malloc_size_of(p); + } + return p; + } + + void deallocate(value_type* const p, const size_t n) { + if constexpr (kIsDmd) { + mMallocSize -= moz_malloc_size_of(p); + } + std::allocator<value_type>{}.deallocate(p, n); + } + + // - + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf) const { + return mMallocSize; + } +}; + +// - + +template <class Key, class T, class Hash = std::hash<Key>, + class KeyEqual = std::equal_to<Key>, + class Allocator = dmd_allocator<std::pair<const Key, T>>, + class _StdT = std::unordered_map<Key, T, Hash, KeyEqual, Allocator>> +class dmd_unordered_map : public _StdT { + public: + using StdT = _StdT; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + const auto& a = StdT::get_allocator(); + return a.SizeOfExcludingThis(mso); + } +}; + +// - + +template <class Key, class Hash = std::hash<Key>, + class KeyEqual = std::equal_to<Key>, + class Allocator = dmd_allocator<Key>, + class _StdT = std::unordered_set<Key, Hash, KeyEqual, Allocator>> +class dmd_unordered_set : public _StdT { + public: + using StdT = _StdT; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + const auto& a = StdT::get_allocator(); + return a.SizeOfExcludingThis(mso); + } +}; + +// - + +} // namespace mozilla::webgl + +#endif // dom_canvas_DmdStdContainers_h diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp index 6055fa72e1..eae6dd78f3 100644 --- a/dom/canvas/DrawTargetWebgl.cpp +++ b/dom/canvas/DrawTargetWebgl.cpp @@ -19,8 +19,6 @@ #include "mozilla/gfx/PathHelpers.h" #include "mozilla/gfx/PathSkia.h" #include "mozilla/gfx/Swizzle.h" -#include "mozilla/layers/CanvasRenderer.h" -#include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/layers/RemoteTextureMap.h" #include "skia/include/core/SkPixmap.h" @@ -467,9 +465,13 @@ bool SharedContextWebgl::Initialize() { const bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting( "Fallback", RFPTarget::WebGLRenderCapability); - const auto initDesc = - webgl::InitContextDesc{/*isWebgl2*/ true, resistFingerprinting, - /*size*/ {1, 1}, options, /*principalKey*/ 0}; + 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); @@ -621,13 +623,14 @@ bool SharedContextWebgl::SetNoClipMask() { } mWebgl->ActiveTexture(1); mWebgl->BindTexture(LOCAL_GL_TEXTURE_2D, mNoClipMask); - static const uint8_t solidMask[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(RawBuffer(Range<const uint8_t>(solidMask, sizeof(solidMask))))}); + 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; @@ -1916,11 +1919,11 @@ bool SharedContextWebgl::UploadSurface(DataSourceSurface* aData, int32_t bpp = BytesPerPixel(aFormat); // Get the data pointer range considering the sampling rect offset and // size. - Range<const uint8_t> range( + Span<const uint8_t> range( map.GetData() + aSrcRect.y * size_t(stride) + aSrcRect.x * bpp, std::max(aSrcRect.height - 1, 0) * size_t(stride) + aSrcRect.width * bpp); - texDesc.cpuData = Some(RawBuffer(range)); + texDesc.cpuData = Some(range); // If the stride happens to be 4 byte aligned, assume that is the // desired alignment regardless of format (even A8). Otherwise, we // default to byte alignment. @@ -4724,7 +4727,8 @@ void DrawTargetWebgl::EndFrame() { } bool DrawTargetWebgl::CopyToSwapChain( - layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId, + layers::TextureType aTextureType, layers::RemoteTextureId aId, + layers::RemoteTextureOwnerId aOwnerId, layers::RemoteTextureOwnerClient* aOwnerClient) { if (!mWebglValid && !FlushFromSkia()) { return false; @@ -4739,11 +4743,8 @@ bool DrawTargetWebgl::CopyToSwapChain( StaticPrefs::gfx_canvas_accelerated_async_present(); options.remoteTextureId = aId; options.remoteTextureOwnerId = aOwnerId; - const RefPtr<layers::ImageBridgeChild> imageBridge = - layers::ImageBridgeChild::GetSingleton(); - auto texType = layers::TexTypeForWebgl(imageBridge); - return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, texType, options, - aOwnerClient); + return mSharedContext->mWebgl->CopyToSwapChain(mFramebuffer, aTextureType, + options, aOwnerClient); } already_AddRefed<DrawTarget> DrawTargetWebgl::CreateSimilarDrawTarget( diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h index 7bc1a83abb..955ccaabf0 100644 --- a/dom/canvas/DrawTargetWebgl.h +++ b/dom/canvas/DrawTargetWebgl.h @@ -577,7 +577,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr { void* GetNativeSurface(NativeSurfaceType aType) override; bool CopyToSwapChain( - layers::RemoteTextureId aId, layers::RemoteTextureOwnerId aOwnerId, + layers::TextureType aTextureType, layers::RemoteTextureId aId, + layers::RemoteTextureOwnerId aOwnerId, layers::RemoteTextureOwnerClient* aOwnerClient = nullptr); void OnMemoryPressure() { mSharedContext->OnMemoryPressure(); } diff --git a/dom/canvas/HostWebGLContext.h b/dom/canvas/HostWebGLContext.h index c508a164c8..cc385bc26a 100644 --- a/dom/canvas/HostWebGLContext.h +++ b/dom/canvas/HostWebGLContext.h @@ -482,8 +482,9 @@ class HostWebGLContext final : public SupportsWeakPtr { return GetWebGL2Context()->GetBufferSubData(target, srcByteOffset, dest); } - void BufferData(GLenum target, const RawBuffer<>& srcData, GLenum usage) const { - mContext->BufferData(target, srcData.size(), srcData.begin(), usage); + void BufferData(GLenum target, const Span<const uint8_t>& srcData, + GLenum usage) const { + mContext->BufferData(target, srcData.size(), srcData.data(), usage); } void BufferData_SizeOnly(GLenum target, size_t byteSize, GLenum usage) const { @@ -491,11 +492,10 @@ class HostWebGLContext final : public SupportsWeakPtr { } void BufferSubData(GLenum target, uint64_t dstByteOffset, - const RawBuffer<>& srcData, + const Span<const uint8_t>& srcData, bool unsynchronized = false) const { - const auto& range = srcData.Data(); - mContext->BufferSubData(target, dstByteOffset, range.length(), - range.begin().get(), unsynchronized); + mContext->BufferSubData(target, dstByteOffset, srcData.size(), + srcData.data(), unsynchronized); } // -------------------------- Framebuffer Objects -------------------------- @@ -507,16 +507,15 @@ class HostWebGLContext final : public SupportsWeakPtr { } void InvalidateFramebuffer(GLenum target, - const RawBuffer<const GLenum>& attachments) const { - GetWebGL2Context()->InvalidateFramebuffer(target, MakeRange(attachments)); + const Span<const GLenum>& attachments) const { + GetWebGL2Context()->InvalidateFramebuffer(target, attachments); } void InvalidateSubFramebuffer(GLenum target, - const RawBuffer<const GLenum>& attachments, - GLint x, GLint y, GLsizei width, - GLsizei height) const { - GetWebGL2Context()->InvalidateSubFramebuffer(target, MakeRange(attachments), - x, y, width, height); + const Span<const GLenum>& attachments, GLint x, + GLint y, GLsizei width, GLsizei height) const { + GetWebGL2Context()->InvalidateSubFramebuffer(target, attachments, x, y, + width, height); } void ReadBuffer(GLenum mode) const { GetWebGL2Context()->ReadBuffer(mode); } @@ -554,10 +553,11 @@ class HostWebGLContext final : public SupportsWeakPtr { // CompressedTexSubImage if `sub` void CompressedTexImage(bool sub, GLenum imageTarget, uint32_t level, GLenum format, const uvec3& offset, const uvec3& size, - const RawBuffer<>& src, const uint32_t pboImageSize, + const Span<const uint8_t>& src, + const uint32_t pboImageSize, const Maybe<uint64_t>& pboOffset) const { mContext->CompressedTexImage(sub, imageTarget, level, format, offset, size, - MakeRange(src), pboImageSize, pboOffset); + src, pboImageSize, pboOffset); } // CopyTexSubImage if `!respecFormat` @@ -603,8 +603,8 @@ class HostWebGLContext final : public SupportsWeakPtr { // ------------------------ Uniforms and attributes ------------------------ void UniformData(uint32_t loc, bool transpose, - const RawBuffer<webgl::UniformDataVal>& data) const { - mContext->UniformData(loc, transpose, data.Data()); + const Span<const webgl::UniformDataVal>& data) const { + mContext->UniformData(loc, transpose, data); } void VertexAttrib4T(GLuint index, const webgl::TypedQuad& data) const { diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp index 65a9feaa56..28641a1c68 100644 --- a/dom/canvas/ImageBitmap.cpp +++ b/dom/canvas/ImageBitmap.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ImageBitmap.h" +#include "mozilla/AppShutdown.h" #include "mozilla/CheckedInt.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/CanvasRenderingContext2D.h" @@ -362,6 +363,7 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) { return nullptr; } + const int bpp = BytesPerPixel(aSurface->GetFormat()); const IntSize srcSize = aSurface->GetSize(); uint8_t* srcBufferPtr = srcMap.GetData(); const uint32_t stride = srcMap.GetStride(); @@ -372,7 +374,8 @@ static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) { } for (int i = 0; i < srcSize.height / 2; ++i) { - std::swap_ranges(srcBufferPtr + stride * i, srcBufferPtr + stride * (i + 1), + std::swap_ranges(srcBufferPtr + stride * i, + srcBufferPtr + stride * i + srcSize.width * bpp, srcBufferPtr + stride * (srcSize.height - 1 - i)); } @@ -1018,7 +1021,6 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal( // handle alpha premultiplication if surface not of correct type gfxAlphaType alphaType = aAlphaType; - bool mustCopy = aMustCopy; bool requiresPremultiply = false; bool requiresUnpremultiply = false; @@ -1027,29 +1029,26 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal( aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { requiresUnpremultiply = true; alphaType = gfxAlphaType::NonPremult; - if (!aAllocatedImageData) { - mustCopy = true; - } } else if (aAlphaType == gfxAlphaType::NonPremult && aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) { requiresPremultiply = true; alphaType = gfxAlphaType::Premult; - if (!aAllocatedImageData) { - mustCopy = true; - } } } /* - * if we don't own the data and need to create a new buffer to flip Y. + * if we don't own the data and need to modify the buffer. * or * we need to crop and flip, where crop must come first. * or - * Or the caller demands a copy (WebGL contexts). + * the caller demands a copy (WebGL contexts). */ - if ((aOptions.mImageOrientation == ImageOrientation::FlipY && - (!aAllocatedImageData || aCropRect.isSome())) || - mustCopy) { + bool willModify = aOptions.mImageOrientation == ImageOrientation::FlipY || + requiresPremultiply || requiresUnpremultiply; + if ((willModify && !aAllocatedImageData) || + (aOptions.mImageOrientation == ImageOrientation::FlipY && + aCropRect.isSome()) || + aMustCopy) { dataSurface = surface->GetDataSurface(); dataSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect); @@ -1430,7 +1429,7 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( return nullptr; } - bool needToReportMemoryAllocation = true; + bool needToReportMemoryAllocation = false; return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, writeOnly, needToReportMemoryAllocation, diff --git a/dom/canvas/ImageUtils.cpp b/dom/canvas/ImageUtils.cpp index 485b87c162..432fa3355e 100644 --- a/dom/canvas/ImageUtils.cpp +++ b/dom/canvas/ImageUtils.cpp @@ -15,27 +15,27 @@ using namespace mozilla::gfx; namespace mozilla::dom { -static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat( +static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromSurfaceFromat( SurfaceFormat aSurfaceFormat) { switch (aSurfaceFormat) { case SurfaceFormat::B8G8R8A8: case SurfaceFormat::B8G8R8X8: - return ImageBitmapFormat::BGRA32; + return Some(ImageBitmapFormat::BGRA32); case SurfaceFormat::R8G8B8A8: case SurfaceFormat::R8G8B8X8: - return ImageBitmapFormat::RGBA32; + return Some(ImageBitmapFormat::RGBA32); case SurfaceFormat::R8G8B8: - return ImageBitmapFormat::RGB24; + return Some(ImageBitmapFormat::RGB24); case SurfaceFormat::B8G8R8: - return ImageBitmapFormat::BGR24; + return Some(ImageBitmapFormat::BGR24); case SurfaceFormat::HSV: - return ImageBitmapFormat::HSV; + return Some(ImageBitmapFormat::HSV); case SurfaceFormat::Lab: - return ImageBitmapFormat::Lab; + return Some(ImageBitmapFormat::Lab); case SurfaceFormat::Depth: - return ImageBitmapFormat::DEPTH; + return Some(ImageBitmapFormat::DEPTH); case SurfaceFormat::A8: - return ImageBitmapFormat::GRAY8; + return Some(ImageBitmapFormat::GRAY8); case SurfaceFormat::R5G6B5_UINT16: case SurfaceFormat::YUV: case SurfaceFormat::NV12: @@ -43,11 +43,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromSurfaceFromat( case SurfaceFormat::P016: case SurfaceFormat::UNKNOWN: default: - return ImageBitmapFormat::EndGuard_; + return Nothing(); } } -static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( +static Maybe<ImageBitmapFormat> GetImageBitmapFormatFromPlanarYCbCrData( layers::PlanarYCbCrData const* aData) { MOZ_ASSERT(aData); @@ -68,11 +68,11 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( !CbInterval.Intersects(CrInterval)) { // Three planes. switch (aData->mChromaSubsampling) { case ChromaSubsampling::FULL: - return ImageBitmapFormat::YUV444P; + return Some(ImageBitmapFormat::YUV444P); case ChromaSubsampling::HALF_WIDTH: - return ImageBitmapFormat::YUV422P; + return Some(ImageBitmapFormat::YUV422P); case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT: - return ImageBitmapFormat::YUV420P; + return Some(ImageBitmapFormat::YUV420P); default: break; } @@ -83,14 +83,14 @@ static ImageBitmapFormat GetImageBitmapFormatFromPlanarYCbCrData( // planes. if (!YInterval.Intersects(CbInterval) && aData->mCbChannel == aData->mCrChannel - 1) { // Two planes. - return ImageBitmapFormat::YUV420SP_NV12; // Y-Cb-Cr + return Some(ImageBitmapFormat::YUV420SP_NV12); // Y-Cb-Cr } else if (!YInterval.Intersects(CrInterval) && aData->mCrChannel == aData->mCbChannel - 1) { // Two planes. - return ImageBitmapFormat::YUV420SP_NV21; // Y-Cr-Cb + return Some(ImageBitmapFormat::YUV420SP_NV21); // Y-Cr-Cb } } - return ImageBitmapFormat::EndGuard_; + return Nothing(); } // ImageUtils::Impl implements the _generic_ algorithm which always readback @@ -104,7 +104,7 @@ class ImageUtils::Impl { virtual ~Impl() = default; - virtual ImageBitmapFormat GetFormat() const { + virtual Maybe<ImageBitmapFormat> GetFormat() const { return GetImageBitmapFormatFromSurfaceFromat(Surface()->GetFormat()); } @@ -144,7 +144,7 @@ class YUVImpl final : public ImageUtils::Impl { aImage->GetFormat() == ImageFormat::NV_IMAGE); } - ImageBitmapFormat GetFormat() const override { + Maybe<ImageBitmapFormat> GetFormat() const override { return GetImageBitmapFormatFromPlanarYCbCrData(GetPlanarYCbCrData()); } @@ -189,7 +189,7 @@ ImageUtils::~ImageUtils() { } } -ImageBitmapFormat ImageUtils::GetFormat() const { +Maybe<ImageBitmapFormat> ImageUtils::GetFormat() const { MOZ_ASSERT(mImpl); return mImpl->GetFormat(); } diff --git a/dom/canvas/ImageUtils.h b/dom/canvas/ImageUtils.h index 46b391515c..6b9cd9ca71 100644 --- a/dom/canvas/ImageUtils.h +++ b/dom/canvas/ImageUtils.h @@ -13,6 +13,9 @@ namespace mozilla { +template <typename T> +class Maybe; + namespace layers { class Image; } @@ -51,7 +54,7 @@ class ImageUtils { explicit ImageUtils(layers::Image* aImage); ~ImageUtils(); - ImageBitmapFormat GetFormat() const; + Maybe<ImageBitmapFormat> GetFormat() const; uint32_t GetBufferLength() const; diff --git a/dom/canvas/OffscreenCanvasDisplayHelper.cpp b/dom/canvas/OffscreenCanvasDisplayHelper.cpp index b1e2ec1694..9e1cbb3e75 100644 --- a/dom/canvas/OffscreenCanvasDisplayHelper.cpp +++ b/dom/canvas/OffscreenCanvasDisplayHelper.cpp @@ -16,6 +16,7 @@ #include "mozilla/layers/PersistentBufferProvider.h" #include "mozilla/layers/TextureClientSharedSurface.h" #include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/StaticPrefs_gfx.h" #include "mozilla/SVGObserverUtils.h" #include "nsICanvasRenderingContextInternal.h" #include "nsRFPService.h" @@ -37,11 +38,25 @@ void OffscreenCanvasDisplayHelper::DestroyElement() { MOZ_ASSERT(NS_IsMainThread()); MutexAutoLock lock(mMutex); + if (mImageContainer) { + mImageContainer->ClearAllImages(); + mImageContainer = nullptr; + } + mFrontBufferSurface = nullptr; mCanvasElement = nullptr; } void OffscreenCanvasDisplayHelper::DestroyCanvas() { + if (auto* cm = gfx::CanvasManagerChild::Get()) { + cm->EndCanvasTransaction(); + } + MutexAutoLock lock(mMutex); + if (mImageContainer) { + mImageContainer->ClearAllImages(); + mImageContainer = nullptr; + } + mFrontBufferSurface = nullptr; mOffscreenCanvas = nullptr; mWorkerRef = nullptr; } @@ -140,6 +155,12 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor( nsICanvasRenderingContextInternal* aContext, layers::TextureType aTextureType, const Maybe<OffscreenCanvasDisplayData>& aData) { + auto endTransaction = MakeScopeExit([&]() { + if (auto* cm = gfx::CanvasManagerChild::Get()) { + cm->EndCanvasTransaction(); + } + }); + MutexAutoLock lock(mMutex); gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; @@ -393,81 +414,110 @@ already_AddRefed<gfx::SourceSurface> OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() { MOZ_ASSERT(NS_IsMainThread()); + class SnapshotWorkerRunnable final : public MainThreadWorkerRunnable { + public: + SnapshotWorkerRunnable(WorkerPrivate* aWorkerPrivate, + OffscreenCanvasDisplayHelper* aDisplayHelper) + : MainThreadWorkerRunnable(aWorkerPrivate, "SnapshotWorkerRunnable"), + mMonitor("SnapshotWorkerRunnable::mMonitor"), + mDisplayHelper(aDisplayHelper) {} + + bool WorkerRun(JSContext*, WorkerPrivate*) override { + // The OffscreenCanvas can only be freed on the worker thread, so we + // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its + // destructor. We just need to make sure we don't call into + // OffscreenCanvas while holding the lock since it calls back into the + // OffscreenCanvasDisplayHelper. + RefPtr<OffscreenCanvas> canvas; + { + MutexAutoLock lock(mDisplayHelper->mMutex); + canvas = mDisplayHelper->mOffscreenCanvas; + } + + // Now that we are on the correct thread, we can extract the snapshot. If + // it is a Skia surface, perform a copy to threading issues. + RefPtr<gfx::SourceSurface> surface; + if (canvas) { + if (auto* context = canvas->GetContext()) { + surface = + context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); + if (surface && surface->GetType() == gfx::SurfaceType::SKIA) { + surface = gfx::Factory::CopyDataSourceSurface( + static_cast<gfx::DataSourceSurface*>(surface.get())); + } + } + } + + MonitorAutoLock lock(mMonitor); + mSurface = std::move(surface); + mComplete = true; + lock.NotifyAll(); + return true; + } + + already_AddRefed<gfx::SourceSurface> Wait(int32_t aTimeoutMs) { + MonitorAutoLock lock(mMonitor); + + TimeDuration timeout = TimeDuration::FromMilliseconds(aTimeoutMs); + while (!mComplete) { + if (lock.Wait(timeout) == CVStatus::Timeout) { + return nullptr; + } + } + + return mSurface.forget(); + } + + private: + Monitor mMonitor; + RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper; + RefPtr<gfx::SourceSurface> mSurface MOZ_GUARDED_BY(mMonitor); + bool mComplete MOZ_GUARDED_BY(mMonitor) = false; + }; + bool hasAlpha; bool isAlphaPremult; gl::OriginPos originPos; - Maybe<uint32_t> managerId; - Maybe<int32_t> childId; HTMLCanvasElement* canvasElement; RefPtr<gfx::SourceSurface> surface; - Maybe<layers::RemoteTextureOwnerId> ownerId; + RefPtr<SnapshotWorkerRunnable> workerRunnable; { MutexAutoLock lock(mMutex); +#ifdef MOZ_WIDGET_ANDROID + // On Android, we cannot both display a GL context and read back the pixels. + if (mCanvasElement) { + return nullptr; + } +#endif + hasAlpha = !mData.mIsOpaque; isAlphaPremult = mData.mIsAlphaPremult; originPos = mData.mOriginPos; - ownerId = mData.mOwnerId; - managerId = mContextManagerId; - childId = mContextChildId; canvasElement = mCanvasElement; - surface = mFrontBufferSurface; - } - - if (surface) { - // We already have a copy of the front buffer in our process. - return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - } - -#ifdef MOZ_WIDGET_ANDROID - // On Android, we cannot both display a GL context and read back the pixels. - if (canvasElement) { - return nullptr; - } -#endif - - if (managerId && childId) { - // We don't have a usable surface, and the context lives in the compositor - // process. - return gfx::CanvasManagerChild::Get()->GetSnapshot( - managerId.value(), childId.value(), ownerId, - hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8, - hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft); - } - - if (!canvasElement) { - return nullptr; - } - - // If we don't have any protocol IDs, or an existing surface, it is possible - // it is a main thread OffscreenCanvas instance. If so, then the element's - // OffscreenCanvas is not neutered and has access to the context. We can use - // that to get the snapshot directly. - const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas(); - if (nsICanvasRenderingContextInternal* context = - offscreenCanvas->GetContext()) { - surface = context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); - surface = TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - if (surface) { - return surface.forget(); + if (mWorkerRef) { + workerRunnable = + MakeRefPtr<SnapshotWorkerRunnable>(mWorkerRef->Private(), this); + workerRunnable->Dispatch(); } } - // Finally, we can try peeking into the image container to see if we are able - // to do a readback via the TextureClient. - if (layers::ImageContainer* container = canvasElement->GetImageContainer()) { - AutoTArray<layers::ImageContainer::OwningImage, 1> images; - uint32_t generationCounter; - container->GetCurrentImages(&images, &generationCounter); - if (!images.IsEmpty()) { - if (layers::Image* image = images.LastElement().mImage) { - surface = image->GetAsSourceSurface(); - return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); - } + if (workerRunnable) { + // We transferred to a DOM worker, so we need to do the readback on the + // owning thread and wait for the result. + surface = workerRunnable->Wait( + StaticPrefs::gfx_offscreencanvas_snapshot_timeout_ms()); + } else if (canvasElement) { + // If we have a context, it is owned by the main thread. + const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas(); + if (nsICanvasRenderingContextInternal* context = + offscreenCanvas->GetContext()) { + surface = + context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); } } - return nullptr; + return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); } already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() { diff --git a/dom/canvas/QueueParamTraits.h b/dom/canvas/QueueParamTraits.h index fbedcdea0b..a67f5abf51 100644 --- a/dom/canvas/QueueParamTraits.h +++ b/dom/canvas/QueueParamTraits.h @@ -98,6 +98,10 @@ template <> struct BytesAlwaysValidT<webgl::UniformDataVal> { static constexpr bool value = true; }; +template <> +struct BytesAlwaysValidT<const webgl::UniformDataVal> { + static constexpr bool value = true; +}; // - @@ -350,13 +354,13 @@ struct EnumSerializer { static bool Read(ConsumerView<U>& aConsumerView, ParamType* aResult) { DataType value; if (!aConsumerView.ReadParam(&value)) { - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"); return false; } if (!EnumValidator::IsLegalValue(static_cast<DataType>(value))) { - CrashReporter::AnnotateCrashReport( - CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"); return false; } diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp index 8598c36227..e13dc1c064 100644 --- a/dom/canvas/TexUnpackBlob.cpp +++ b/dom/canvas/TexUnpackBlob.cpp @@ -476,8 +476,7 @@ bool TexUnpackBytes::Validate(const WebGLContext* const webgl, CheckedInt<size_t> availBytes = 0; if (mDesc.cpuData) { - const auto& range = mDesc.cpuData->Data(); - availBytes = range.length(); + availBytes = mDesc.cpuData->size(); } else if (mDesc.pboOffset) { const auto& pboOffset = *mDesc.pboOffset; @@ -514,11 +513,7 @@ bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const uint8_t* uploadPtr = nullptr; if (mDesc.cpuData) { - const auto range = mDesc.cpuData->Data(); - uploadPtr = range.begin().get(); - if (!uploadPtr) { - MOZ_ASSERT(!range.length()); - } + uploadPtr = mDesc.cpuData->data(); } else if (mDesc.pboOffset) { uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset); } @@ -673,11 +668,10 @@ bool TexUnpackImage::Validate(const WebGLContext* const webgl, return ValidateUnpackPixels(webgl, pi, fullRows, *this); } -Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset, - const GLenum internalFormat, - const webgl::PackingInfo& pi, - const TexUnpackBlobDesc& desc, - const bool isRgb8Renderable) { +Maybe<std::string> BlitPreventReason( + const int32_t level, const ivec3& offset, const GLenum internalFormat, + const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc, + const OptionalRenderableFormatBits optionalRenderableFormatBits) { const auto& size = desc.size; const auto& unpacking = desc.unpacking; @@ -710,26 +704,71 @@ Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset, const auto formatReason = [&]() -> const char* { if (pi.type != LOCAL_GL_UNSIGNED_BYTE) { - return "`type` must be `UNSIGNED_BYTE`"; + return "`unpackType` must be `UNSIGNED_BYTE`"; } - switch (internalFormat) { + switch (pi.format) { case LOCAL_GL_RGBA: - case LOCAL_GL_RGBA8: - return nullptr; + return nullptr; // All internalFormats for unpackFormat=RGBA are + // renderable. case LOCAL_GL_RGB: + break; + + default: + return "`unpackFormat` must be `RGBA` or maybe `RGB`"; + } + + // - + + struct { + OptionalRenderableFormatBits bits; + const char* errorMsg; + } required; + + switch (internalFormat) { + case LOCAL_GL_RGB565: + return nullptr; + case LOCAL_GL_RGB: case LOCAL_GL_RGB8: - if (isRgb8Renderable) { - return nullptr; - } + required = { + OptionalRenderableFormatBits::RGB8, + "Unavailable, as blitting internalFormats RGB or RGB8 requires " + "that RGB8 must be a renderable format.", + }; + break; + case LOCAL_GL_SRGB: + case LOCAL_GL_SRGB8: + required = { + OptionalRenderableFormatBits::SRGB8, + "Unavailable, as blitting internalFormats SRGB or SRGB8 requires " + "that SRGB8 must be a renderable format.", + }; + break; + case 0: + // texSubImage, so internalFormat is unknown, and could be anything! + required = { + OptionalRenderableFormatBits::RGB8 | + OptionalRenderableFormatBits::SRGB8, + "Unavailable, as blitting texSubImage with unpackFormat=RGB " + "requires that RGB8 and SRGB8 must be renderable formats.", + }; break; + default: + gfxCriticalError() + << "Unexpected internalFormat for unpackFormat=RGB: 0x" + << gfx::hexa(internalFormat); + return "Unexpected internalFormat for unpackFormat=RGB"; } - if (isRgb8Renderable) { - return "effective format must be RGB8 or RGBA8"; - } else { - return "effective format must be RGBA8"; + + const auto availableBits = optionalRenderableFormatBits; + if ((required.bits | availableBits) != availableBits) { + return required.errorMsg; } + + // - + + return nullptr; }(); if (formatReason) return formatReason; @@ -761,7 +800,7 @@ bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const auto reason = BlitPreventReason(level, {xOffset, yOffset, zOffset}, dui->internalFormat, - pi, mDesc, webgl->mIsRgb8Renderable); + pi, mDesc, webgl->mOptionalRenderableFormatBits); if (reason) { webgl->GeneratePerfWarning( "Failed to hit GPU-copy fast-path." diff --git a/dom/canvas/TexUnpackBlob.h b/dom/canvas/TexUnpackBlob.h index 02cb8dec41..dc850fe5d4 100644 --- a/dom/canvas/TexUnpackBlob.h +++ b/dom/canvas/TexUnpackBlob.h @@ -45,7 +45,7 @@ Maybe<std::string> BlitPreventReason(int32_t level, const ivec3& offset, GLenum internalFormat, const webgl::PackingInfo&, const TexUnpackBlobDesc&, - bool isRgb8Renderable); + OptionalRenderableFormatBits); class TexUnpackBlob { public: diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h index 138dd85b80..018c81f298 100644 --- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -47,9 +47,9 @@ class WebGL2Context final : public WebGLContext { GLbitfield mask, GLenum filter); void InvalidateFramebuffer(GLenum target, - const Range<const GLenum>& attachments); + const Span<const GLenum>& attachments); void InvalidateSubFramebuffer(GLenum target, - const Range<const GLenum>& attachments, GLint x, + const Span<const GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height); void ReadBuffer(GLenum mode); diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp index 7056c83d03..ab9e848f62 100644 --- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -106,7 +106,7 @@ static bool ValidateFramebufferAttachmentEnum(WebGLContext* webgl, } bool WebGLContext::ValidateInvalidateFramebuffer( - GLenum target, const Range<const GLenum>& attachments, + GLenum target, const Span<const GLenum>& attachments, std::vector<GLenum>* const scopedVector, GLsizei* const out_glNumAttachments, const GLenum** const out_glAttachments) { @@ -139,8 +139,8 @@ bool WebGLContext::ValidateInvalidateFramebuffer( } DoBindFB(fb, target); - *out_glNumAttachments = attachments.length(); - *out_glAttachments = attachments.begin().get(); + *out_glNumAttachments = AutoAssertCast(attachments.size()); + *out_glAttachments = attachments.data(); if (fb) { for (const auto& attachment : attachments) { @@ -153,7 +153,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( if (!isDefaultFB) { MOZ_ASSERT(scopedVector->empty()); - scopedVector->reserve(attachments.length()); + scopedVector->reserve(attachments.size()); for (const auto& attachment : attachments) { switch (attachment) { case LOCAL_GL_COLOR: @@ -172,7 +172,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( MOZ_CRASH(); } } - *out_glNumAttachments = scopedVector->size(); + *out_glNumAttachments = AutoAssertCast(scopedVector->size()); *out_glAttachments = scopedVector->data(); } } @@ -183,7 +183,7 @@ bool WebGLContext::ValidateInvalidateFramebuffer( } void WebGL2Context::InvalidateFramebuffer( - GLenum target, const Range<const GLenum>& attachments) { + GLenum target, const Span<const GLenum>& attachments) { const FuncScope funcScope(*this, "invalidateFramebuffer"); std::vector<GLenum> scopedVector; @@ -210,7 +210,7 @@ void WebGL2Context::InvalidateFramebuffer( } void WebGL2Context::InvalidateSubFramebuffer( - GLenum target, const Range<const GLenum>& attachments, GLint x, GLint y, + GLenum target, const Span<const GLenum>& attachments, GLint x, GLint y, GLsizei width, GLsizei height) { const FuncScope funcScope(*this, "invalidateSubFramebuffer"); diff --git a/dom/canvas/WebGLChild.cpp b/dom/canvas/WebGLChild.cpp index cf676c2f53..ebfe35db88 100644 --- a/dom/canvas/WebGLChild.cpp +++ b/dom/canvas/WebGLChild.cpp @@ -87,10 +87,8 @@ void WebGLChild::FlushPendingCmds() { // Handle flushesSinceLastCongestionCheck mFlushedCmdInfo.flushesSinceLastCongestionCheck += 1; - const auto startCongestionCheck = 20; - const auto maybeIPCMessageCongestion = 70; - const auto eventTarget = GetCurrentSerialEventTarget(); - MOZ_ASSERT(eventTarget); + constexpr auto START_CONGESTION_CHECK_THRESHOLD = 20; + constexpr auto ASSUME_IPC_CONGESTION_THRESHOLD = 70; RefPtr<WebGLChild> self = this; size_t generation = self->mFlushedCmdInfo.congestionCheckGeneration; @@ -107,17 +105,24 @@ void WebGLChild::FlushPendingCmds() { // messages. // Due to the async nature of the async ping, it is possible for the flush // check to exceed maybeIPCMessageCongestion, but that it it still bounded. - if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == startCongestionCheck) { - SendPing()->Then(eventTarget, __func__, [self, generation]() { - if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) { - // Confirmed IPC messages congestion does not happen. - // Reset flushesSinceLastCongestionCheck for next congestion check. - self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; - self->mFlushedCmdInfo.congestionCheckGeneration++; - } - }); + if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == + START_CONGESTION_CHECK_THRESHOLD) { + const auto eventTarget = RefPtr{GetCurrentSerialEventTarget()}; + MOZ_ASSERT(eventTarget); + if (!eventTarget) { + NS_WARNING("GetCurrentSerialEventTarget()->nullptr in FlushPendingCmds."); + } else { + SendPing()->Then(eventTarget, __func__, [self, generation]() { + if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) { + // Confirmed IPC messages congestion does not happen. + // Reset flushesSinceLastCongestionCheck for next congestion check. + self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; + self->mFlushedCmdInfo.congestionCheckGeneration++; + } + }); + } } else if (mFlushedCmdInfo.flushesSinceLastCongestionCheck > - maybeIPCMessageCongestion) { + ASSUME_IPC_CONGESTION_THRESHOLD) { // IPC messages congestion might happen, send sync SyncPing for flushing // pending messages. SendSyncPing(); diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index c1e6cbbed2..ec19bfef3d 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -653,7 +653,7 @@ RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext* host, out->limits = *webgl->mLimits; out->uploadableSdTypes = UploadableSdTypes(); out->vendor = webgl->gl->Vendor(); - out->isRgb8Renderable = webgl->mIsRgb8Renderable; + out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits; return webgl; } @@ -710,15 +710,36 @@ void WebGLContext::FinishInit() { const auto tex = gl::ScopedTexture(gl); const auto fb = gl::ScopedFramebuffer(gl); gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex); - gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, 1, 1, 0, LOCAL_GL_RGB, - LOCAL_GL_UNSIGNED_BYTE, nullptr); - gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, tex, 0); - const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); - mIsRgb8Renderable = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); + const auto IsRenderable = [&](const GLint internalFormat, + const GLenum unpackFormat) { + gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, + unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr); + const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); + }; + + if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) { + mOptionalRenderableFormatBits |= + webgl::OptionalRenderableFormatBits::RGB8; + } + if (gl->IsSupported(gl::GLFeature::sRGB)) { + struct { + GLint internal; + GLenum unpack; + } formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB}; + const bool isEs2 = (gl->IsGLES() && gl->Version() < 300); + if (isEs2) { + formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB}; + } + if (IsRenderable(formats.internal, formats.unpack)) { + mOptionalRenderableFormatBits |= + webgl::OptionalRenderableFormatBits::SRGB8; + } + } } ////// diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 12727c4969..5ab584174c 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -317,7 +317,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { webgl::InitContextResult* out); private: - bool mIsRgb8Renderable = false; + webgl::OptionalRenderableFormatBits mOptionalRenderableFormatBits = + webgl::OptionalRenderableFormatBits{0}; void FinishInit(); protected: @@ -667,7 +668,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { ////////////////////////// void UniformData(uint32_t loc, bool transpose, - const Range<const webgl::UniformDataVal>& data) const; + const Span<const webgl::UniformDataVal>& data) const; //////////////////////////////////// @@ -968,8 +969,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { // ------------------------------------------------------------------------- // WebGL extensions (implemented in WebGLContextExtensions.cpp) - EnumeratedArray<WebGLExtensionID, WebGLExtensionID::Max, - std::unique_ptr<WebGLExtensionBase>> + EnumeratedArray<WebGLExtensionID, std::unique_ptr<WebGLExtensionBase>, + size_t(WebGLExtensionID::Max)> mExtensions; public: @@ -1156,7 +1157,7 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr { bool ValidateFramebufferTarget(GLenum target) const; bool ValidateInvalidateFramebuffer(GLenum target, - const Range<const GLenum>& attachments, + const Span<const GLenum>& attachments, std::vector<GLenum>* const scopedVector, GLsizei* const out_glNumAttachments, const GLenum** const out_glAttachments); diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp index 7411cec02f..ef068c8999 100644 --- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -1280,7 +1280,7 @@ void WebGLContext::StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, void WebGLContext::UniformData( const uint32_t loc, const bool transpose, - const Range<const webgl::UniformDataVal>& data) const { + const Span<const webgl::UniformDataVal>& data) const { const FuncScope funcScope(*this, "uniform setter"); if (!IsWebGL2() && transpose) { @@ -1306,13 +1306,13 @@ void WebGLContext::UniformData( // - - const auto lengthInType = data.length(); + const auto lengthInType = data.size(); const auto elemCount = lengthInType / channels; if (elemCount > 1 && !validationInfo.isArray) { GenerateError( LOCAL_GL_INVALID_OPERATION, "(uniform %s) `values` length (%u) must exactly match size of %s.", - activeInfo.name.c_str(), lengthInType, + activeInfo.name.c_str(), (uint32_t)lengthInType, EnumString(activeInfo.elemType).c_str()); return; } @@ -1321,9 +1321,9 @@ void WebGLContext::UniformData( const auto& samplerInfo = locInfo->samplerInfo; if (samplerInfo) { - const auto idata = reinterpret_cast<const uint32_t*>(data.begin().get()); + const auto idata = ReinterpretToSpan<const uint32_t>::From(data); const auto maxTexUnits = GLMaxTextureUnits(); - for (const auto& val : Range<const uint32_t>(idata, elemCount)) { + for (const auto& val : idata) { if (val >= maxTexUnits) { ErrorInvalidValue( "This uniform location is a sampler, but %d" @@ -1337,7 +1337,7 @@ void WebGLContext::UniformData( // - // This is a little galaxy-brain, sorry! - const auto ptr = static_cast<const void*>(data.begin().get()); + const auto ptr = static_cast<const void*>(data.data()); (*pfn)(*gl, static_cast<GLint>(loc), elemCount, transpose, ptr); // - @@ -1345,12 +1345,12 @@ void WebGLContext::UniformData( if (samplerInfo) { auto& texUnits = samplerInfo->texUnits; - const auto srcBegin = reinterpret_cast<const uint32_t*>(data.begin().get()); + const auto srcBegin = reinterpret_cast<const uint32_t*>(data.data()); auto destIndex = locInfo->indexIntoUniform; if (destIndex < texUnits.length()) { // Only sample as many indexes as available tex units allow. const auto destCount = std::min(elemCount, texUnits.length() - destIndex); - for (const auto& val : Range<const uint32_t>(srcBegin, destCount)) { + for (const auto& val : Span<const uint32_t>(srcBegin, destCount)) { texUnits[destIndex] = AssertedCast<uint8_t>(val); destIndex += 1; } diff --git a/dom/canvas/WebGLIpdl.h b/dom/canvas/WebGLIpdl.h index 748c1a30a6..5a35c05909 100644 --- a/dom/canvas/WebGLIpdl.h +++ b/dom/canvas/WebGLIpdl.h @@ -10,6 +10,7 @@ #include "ipc/EnumSerializer.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/GfxMessageUtils.h" +#include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "mozilla/ipc/Shmem.h" #include "mozilla/layers/LayersSurfaces.h" @@ -233,6 +234,16 @@ struct ParamTraits<gfxAlphaType> : public ContiguousEnumSerializerInclusive< gfxAlphaType, gfxAlphaType::Opaque, gfxAlphaType::NonPremult> {}; +template <> +struct ParamTraits<mozilla::dom::WebGLPowerPreference> final + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::WebGLPowerPreference> {}; + +template <> +struct ParamTraits<mozilla::dom::PredefinedColorSpace> final + : public mozilla::dom::WebIDLEnumSerializer< + mozilla::dom::PredefinedColorSpace> {}; + // - // ParamTraits_TiedFields @@ -242,7 +253,7 @@ struct ParamTraits_TiedFields { static void Write(MessageWriter* const writer, const T& in) { const auto& fields = mozilla::TiedFields(in); - MapTuple(fields, [&](const auto& field) { + mozilla::MapTuple(fields, [&](const auto& field) { WriteParam(writer, field); return true; // ignored }); @@ -251,7 +262,7 @@ struct ParamTraits_TiedFields { static bool Read(MessageReader* const reader, T* const out) { const auto& fields = mozilla::TiedFields(*out); bool ok = true; - MapTuple(fields, [&](auto& field) { + mozilla::MapTuple(fields, [&](auto& field) { if (ok) { ok &= ReadParam(reader, &field); } @@ -263,72 +274,17 @@ struct ParamTraits_TiedFields { // - -template <typename T> -bool ValidateParam(const T& val) { - return ParamTraits<T>::Validate(val); -} - -template <typename T> -struct ValidatedPlainOldDataSerializer : public PlainOldDataSerializer<T> { - static void Write(MessageWriter* const writer, const T& in) { - MOZ_ASSERT(ValidateParam(in)); - PlainOldDataSerializer<T>::Write(writer, in); - } - - static bool Read(MessageReader* const reader, T* const out) { - if (!PlainOldDataSerializer<T>::Read(reader, out)) return false; - return ValidateParam(*out); - } - - // static bool Validate(const T&) = 0; -}; - -// - - template <> struct ParamTraits<mozilla::webgl::InitContextDesc> final - : public ValidatedPlainOldDataSerializer<mozilla::webgl::InitContextDesc> { - using T = mozilla::webgl::InitContextDesc; - - static bool Validate(const T& val) { - return ValidateParam(val.options) && (val.size.x && val.size.y); - } -}; + : public ParamTraits_TiedFields<mozilla::webgl::InitContextDesc> {}; template <> struct ParamTraits<mozilla::WebGLContextOptions> final - : public ValidatedPlainOldDataSerializer<mozilla::WebGLContextOptions> { - using T = mozilla::WebGLContextOptions; - - static bool Validate(const T& val) { - bool ok = true; - ok &= ValidateParam(val.powerPreference); - ok &= ValidateParam(val.colorSpace); - return ok; - } -}; - -template <> -struct ParamTraits<mozilla::dom::WebGLPowerPreference> final - : public ValidatedPlainOldDataSerializer< - mozilla::dom::WebGLPowerPreference> { - using T = mozilla::dom::WebGLPowerPreference; - - static bool Validate(const T& val) { return val <= T::High_performance; } -}; - -template <> -struct ParamTraits<mozilla::dom::PredefinedColorSpace> final - : public ValidatedPlainOldDataSerializer< - mozilla::dom::PredefinedColorSpace> { - using T = mozilla::dom::PredefinedColorSpace; - - static bool Validate(const T& val) { return val < T::EndGuard_; } -}; + : public ParamTraits_TiedFields<mozilla::WebGLContextOptions> {}; template <> struct ParamTraits<mozilla::webgl::OpaqueFramebufferOptions> final - : public PlainOldDataSerializer<mozilla::webgl::OpaqueFramebufferOptions> { + : public ParamTraits_TiedFields<mozilla::webgl::OpaqueFramebufferOptions> { }; // - @@ -340,28 +296,24 @@ struct ParamTraits<mozilla::gl::GLVendor> mozilla::gl::kHighestGLVendor> { }; -template <typename T> -struct ParamTraits<mozilla::webgl::EnumMask<T>> final - : public PlainOldDataSerializer<mozilla::webgl::EnumMask<T>> {}; +template <typename U> +struct ParamTraits<mozilla::webgl::EnumMask<U>> final + : public ParamTraits_TiedFields<mozilla::webgl::EnumMask<U>> {}; template <> struct ParamTraits<mozilla::webgl::InitContextResult> final : public ParamTraits_TiedFields<mozilla::webgl::InitContextResult> {}; template <> -struct ParamTraits<mozilla::webgl::ExtensionBits> final - : public PlainOldDataSerializer<mozilla::webgl::ExtensionBits> {}; - -template <> struct ParamTraits<mozilla::webgl::Limits> final - : public PlainOldDataSerializer<mozilla::webgl::Limits> {}; + : public ParamTraits_TiedFields<mozilla::webgl::Limits> {}; template <> struct ParamTraits<mozilla::webgl::PixelPackingState> final - : public PlainOldDataSerializer<mozilla::webgl::PixelPackingState> {}; + : public ParamTraits_TiedFields<mozilla::webgl::PixelPackingState> {}; template <> struct ParamTraits<mozilla::webgl::PixelUnpackStateWebgl> final - : public PlainOldDataSerializer<mozilla::webgl::PixelUnpackStateWebgl> {}; + : public ParamTraits_TiedFields<mozilla::webgl::PixelUnpackStateWebgl> {}; // - @@ -570,6 +522,24 @@ struct ParamTraits<mozilla::webgl::ShaderPrecisionFormat> final { // - template <typename U, size_t N> +struct ParamTraits<std::array<U, N>> final { + using T = std::array<U, N>; + + static void Write(MessageWriter* const writer, const T& in) { + for (const auto& v : in) { + WriteParam(writer, v); + } + } + + static bool Read(MessageReader* const reader, T* const out) { + for (auto& v : *out) { + if (!ReadParam(reader, &v)) return false; + } + return true; + } +}; + +template <typename U, size_t N> struct ParamTraits<U[N]> final { using T = U[N]; static constexpr size_t kByteSize = sizeof(U) * N; @@ -639,6 +609,35 @@ struct ParamTraits<mozilla::avec3<U>> final { } }; +// - + +template <class TT> +struct ParamTraits_IsEnumCase { + using T = TT; + + static void Write(IPC::MessageWriter* const writer, const T& in) { + MOZ_RELEASE_ASSERT(IsEnumCase(in)); + WriteParam(writer, mozilla::UnderlyingValue(in)); + } + + static bool Read(IPC::MessageReader* const reader, T* const out) { + std::underlying_type_t<T> rawVal; + if (!ReadParam(reader, &rawVal)) return false; + *out = static_cast<T>(rawVal); + return IsEnumCase(*out); + } +}; + +// - + +#define USE_IS_ENUM_CASE(T) \ + template <> \ + struct ParamTraits<T> : public ParamTraits_IsEnumCase<T> {}; + +USE_IS_ENUM_CASE(mozilla::webgl::OptionalRenderableFormatBits) + +#undef USE_IS_ENUM_CASE + } // namespace IPC #endif diff --git a/dom/canvas/WebGLMemoryTracker.cpp b/dom/canvas/WebGLMemoryTracker.cpp index 361d1be695..19d3847acc 100644 --- a/dom/canvas/WebGLMemoryTracker.cpp +++ b/dom/canvas/WebGLMemoryTracker.cpp @@ -12,8 +12,9 @@ #include "WebGLTexture.h" namespace mozilla { - -MOZ_DEFINE_MALLOC_SIZE_OF(WebGLShaderMallocSizeOf) +namespace webgl { +MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) +} // namespace webgl void WebGLMemoryTracker::EnsureRegistered() { static bool sIsRegistered = []() { @@ -42,6 +43,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, int64_t shaderCpuSize = 0; size_t texCount = 0; + size_t texHeapOverhead = 0; int64_t texGpuSize = 0; for (const auto& context : contexts) { @@ -70,7 +72,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, shaderCount += context->mShaderMap.size(); for (const auto& pair : context->mShaderMap) { const auto& shader = pair.second; - shaderCpuSize += shader->SizeOfIncludingThis(WebGLShaderMallocSizeOf); + shaderCpuSize += shader->SizeOfIncludingThis(webgl::MallocSizeOf); } // - @@ -79,6 +81,7 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, for (const auto& pair : context->mTextureMap) { const auto& texture = pair.second; texGpuSize += texture->MemoryUsage(); + texHeapOverhead += texture->SizeOfIncludingThis(webgl::MallocSizeOf); } } @@ -126,10 +129,14 @@ WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, "Number of WebGL renderbuffers."); MOZ_COLLECT_REPORT( - "explicit/webgl/shader", KIND_HEAP, UNITS_BYTES, shaderCpuSize, + "explicit/webgl/shaders", KIND_HEAP, UNITS_BYTES, shaderCpuSize, "Combined size of WebGL shader ASCII sources and translation logs " "cached on the heap."); + MOZ_COLLECT_REPORT("explicit/webgl/textures", KIND_HEAP, UNITS_BYTES, + texHeapOverhead, + "WebGLTexture implementation detail overhead."); + MOZ_COLLECT_REPORT("webgl-shader-count", KIND_OTHER, UNITS_COUNT, static_cast<int64_t>(shaderCount), "Number of WebGL shaders."); diff --git a/dom/canvas/WebGLQueueParamTraits.h b/dom/canvas/WebGLQueueParamTraits.h index db3ef0a44b..3c74f08750 100644 --- a/dom/canvas/WebGLQueueParamTraits.h +++ b/dom/canvas/WebGLQueueParamTraits.h @@ -67,8 +67,6 @@ inline constexpr bool IsEnumCase(const dom::WebGLPowerPreference raw) { case dom::WebGLPowerPreference::Low_power: case dom::WebGLPowerPreference::High_performance: return true; - case dom::WebGLPowerPreference::EndGuard_: - break; } return false; } @@ -78,8 +76,6 @@ inline constexpr bool IsEnumCase(const dom::PredefinedColorSpace raw) { case dom::PredefinedColorSpace::Srgb: case dom::PredefinedColorSpace::Display_p3: return true; - case dom::PredefinedColorSpace::EndGuard_: - break; } return false; } @@ -114,23 +110,21 @@ USE_IS_ENUM_CASE(webgl::ProvokingVertex) // Custom QueueParamTraits template <typename T> -struct QueueParamTraits<RawBuffer<T>> { - using ParamType = RawBuffer<T>; - +struct QueueParamTraits<Span<T>> { template <typename U> - static bool Write(ProducerView<U>& view, const ParamType& in) { + static bool Write(ProducerView<U>& view, const Span<T>& in) { const auto& elemCount = in.size(); auto status = view.WriteParam(elemCount); if (!status) return status; if (!elemCount) return status; - status = view.WriteFromRange(in.Data()); + status = view.WriteFromRange(Range<const T>{in}); return status; } template <typename U> - static bool Read(ConsumerView<U>& view, ParamType* const out) { + static bool Read(ConsumerView<U>& view, Span<const T>* const out) { size_t elemCount = 0; auto status = view.ReadParam(&elemCount); if (!status) return status; @@ -140,9 +134,9 @@ struct QueueParamTraits<RawBuffer<T>> { return true; } - auto data = view.template ReadRange<T>(elemCount); + auto data = view.template ReadRange<const T>(elemCount); if (!data) return false; - *out = std::move(RawBuffer<T>{*data}); + *out = Span{*data}; return true; } }; diff --git a/dom/canvas/WebGLShaderValidator.h b/dom/canvas/WebGLShaderValidator.h index ab05528f19..7fc700937f 100644 --- a/dom/canvas/WebGLShaderValidator.h +++ b/dom/canvas/WebGLShaderValidator.h @@ -39,7 +39,7 @@ class ShaderValidatorResults final { bool CanLinkTo(const ShaderValidatorResults& vert, nsCString* const out_log) const; - size_t SizeOfIncludingThis(MallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const; }; class ShaderValidator final { diff --git a/dom/canvas/WebGLTexture.h b/dom/canvas/WebGLTexture.h index 8a451a9277..7d75bf50f6 100644 --- a/dom/canvas/WebGLTexture.h +++ b/dom/canvas/WebGLTexture.h @@ -203,6 +203,14 @@ class WebGLTexture final : public WebGLContextBoundObject, ~WebGLTexture() override; public: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf mso) const { + return CacheInvalidator::SizeOfExcludingThis(mso) + + mSamplingCache.SizeOfExcludingThis(mso); + } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mso) const { + return mso(this) + SizeOfExcludingThis(mso); + } + //////////////////////////////////// // GL calls bool BindTexture(TexTarget texTarget); diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp index 6686c34178..1a439ca5bc 100644 --- a/dom/canvas/WebGLTextureUpload.cpp +++ b/dom/canvas/WebGLTextureUpload.cpp @@ -60,6 +60,10 @@ Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size, } const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface; + if (NS_WARN_IF(!surf)) { + return {}; + } + const auto imageSize = *uvec2::FromSize(surf->GetSize()); if (!size) { size.emplace(imageSize.x, imageSize.y, 1); @@ -925,23 +929,7 @@ void WebGLTexture::TexStorage(TexTarget target, uint32_t levels, void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat, const uvec3& offset, const webgl::PackingInfo& pi, const webgl::TexUnpackBlobDesc& src) { - Maybe<RawBuffer<>> cpuDataView; - if (src.cpuData) { - cpuDataView = Some(RawBuffer<>{src.cpuData->Data()}); - } - const auto srcViewDesc = webgl::TexUnpackBlobDesc{src.imageTarget, - src.size, - src.srcAlphaType, - std::move(cpuDataView), - src.pboOffset, - src.structuredSrcSize, - src.image, - src.sd, - src.dataSurf, - src.unpacking, - src.applyUnpackTransforms}; - - const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc); + const auto blob = webgl::TexUnpackBlob::Create(src); if (!blob) { MOZ_ASSERT(false); return; diff --git a/dom/canvas/WebGLTypes.h b/dom/canvas/WebGLTypes.h index b23839b9ca..c268047930 100644 --- a/dom/canvas/WebGLTypes.h +++ b/dom/canvas/WebGLTypes.h @@ -19,11 +19,14 @@ #include "ImageContainer.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" +#include "mozilla/EnumTypeTraits.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Range.h" #include "mozilla/RefCounted.h" #include "mozilla/Result.h" #include "mozilla/ResultVariant.h" +#include "mozilla/Span.h" +#include "mozilla/TypedEnumBits.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/BuildConstants.h" #include "mozilla/gfx/Logging.h" @@ -407,8 +410,6 @@ inline ColorSpace2 ToColorSpace2(const dom::PredefinedColorSpace cs) { return ColorSpace2::SRGB; case dom::PredefinedColorSpace::Display_p3: return ColorSpace2::DISPLAY_P3; - case dom::PredefinedColorSpace::EndGuard_: - break; } MOZ_CRASH("Exhaustive switch"); } @@ -598,9 +599,13 @@ class EnumMask { public: BitRef operator[](const E i) { return {*this, Mask(i)}; } bool operator[](const E i) const { return mBits & Mask(i); } + + // - + + auto MutTiedFields() { return std::tie(mBits); } }; -class ExtensionBits : public EnumMask<WebGLExtensionID> {}; +using ExtensionBits = EnumMask<WebGLExtensionID>; // - @@ -624,9 +629,16 @@ inline bool ReadContextLossReason(const uint8_t val, struct InitContextDesc final { bool isWebgl2 = false; bool resistFingerprinting = false; + std::array<uint8_t, 2> _padding; + uint32_t principalKey = 0; uvec2 size = {}; WebGLContextOptions options; - uint32_t principalKey = 0; + std::array<uint8_t, 3> _padding2; + + auto MutTiedFields() { + return std::tie(isWebgl2, resistFingerprinting, _padding, principalKey, + size, options, _padding2); + } }; constexpr uint32_t kMaxTransformFeedbackSeparateAttribs = 4; @@ -651,10 +663,24 @@ struct Limits final { // Exts bool astcHdr = false; + std::array<uint8_t, 3> _padding; uint32_t maxColorDrawBuffers = 1; + uint32_t maxMultiviewLayers = 0; uint64_t queryCounterBitsTimeElapsed = 0; uint64_t queryCounterBitsTimestamp = 0; - uint32_t maxMultiviewLayers = 0; + + auto MutTiedFields() { + return std::tie(supportedExtensions, + + maxTexUnits, maxTex2dSize, maxTexCubeSize, maxVertexAttribs, + maxViewportDim, pointSizeRange, lineWidthRange, + + maxTexArrayLayers, maxTex3dSize, maxUniformBufferBindings, + uniformBufferOffsetAlignment, + + astcHdr, _padding, maxColorDrawBuffers, maxMultiviewLayers, + queryCounterBitsTimeElapsed, queryCounterBitsTimestamp); + } }; // - @@ -680,18 +706,41 @@ struct Padded { // - +enum class OptionalRenderableFormatBits : uint8_t { + RGB8 = (1 << 0), + SRGB8 = (1 << 1), +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OptionalRenderableFormatBits) +inline constexpr bool IsEnumCase(const OptionalRenderableFormatBits raw) { + auto rawWithoutValidBits = UnderlyingValue(raw); + auto bit = decltype(rawWithoutValidBits){1}; + while (bit) { + switch (OptionalRenderableFormatBits{bit}) { + // -Werror=switch ensures exhaustive. + case OptionalRenderableFormatBits::RGB8: + case OptionalRenderableFormatBits::SRGB8: + rawWithoutValidBits &= ~bit; + break; + } + bit <<= 1; + } + return rawWithoutValidBits == 0; +} + +// - + struct InitContextResult final { Padded<std::string, 32> error; // MINGW 32-bit needs this padding. WebGLContextOptions options; gl::GLVendor vendor; - bool isRgb8Renderable; + OptionalRenderableFormatBits optionalRenderableFormatBits; uint8_t _padding = {}; - webgl::Limits limits; + Limits limits; EnumMask<layers::SurfaceDescriptor::Type> uploadableSdTypes; auto MutTiedFields() { - return std::tie(error, options, vendor, isRgb8Renderable, _padding, limits, - uploadableSdTypes); + return std::tie(error, options, vendor, optionalRenderableFormatBits, + _padding, limits, uploadableSdTypes); } }; @@ -732,8 +781,13 @@ struct CompileResult final { struct OpaqueFramebufferOptions final { bool depthStencil = true; bool antialias = true; + std::array<uint8_t, 2> _padding; uint32_t width = 0; uint32_t height = 0; + + auto MutTiedFields() { + return std::tie(depthStencil, antialias, _padding, width, height); + } }; // - @@ -853,47 +907,6 @@ struct VertAttribPointerCalculated final { } // namespace webgl -// TODO: s/RawBuffer/Span/ -template <typename T = uint8_t> -class RawBuffer final { - const T* mBegin = nullptr; - size_t mLen = 0; - - public: - using ElementType = T; - - explicit RawBuffer(const Range<const T>& data) - : mBegin(data.begin().get()), mLen(data.length()) { - if (mLen) { - MOZ_ASSERT(mBegin); - } - } - - ~RawBuffer() = default; - - Range<const T> Data() const { return {begin(), mLen}; } - const auto& begin() const { - if (mLen) { - MOZ_RELEASE_ASSERT(mBegin); - } - return mBegin; - } - const auto& size() const { return mLen; } - - void Shrink(const size_t newLen) { - if (mLen <= newLen) return; - mLen = newLen; - } - - RawBuffer() = default; - - RawBuffer(const RawBuffer&) = delete; - RawBuffer& operator=(const RawBuffer&) = delete; - - RawBuffer(RawBuffer&&) = default; - RawBuffer& operator=(RawBuffer&&) = default; -}; - template <class T> inline Range<T> ShmemRange(const mozilla::ipc::Shmem& shmem) { return {shmem.get<T>(), shmem.Size<T>()}; @@ -1096,7 +1109,7 @@ struct TexUnpackBlobDesc final { uvec3 size; gfxAlphaType srcAlphaType = gfxAlphaType::NonPremult; - Maybe<RawBuffer<>> cpuData; + Maybe<Span<const uint8_t>> cpuData; Maybe<uint64_t> pboOffset; Maybe<uvec2> structuredSrcSize; @@ -1134,11 +1147,6 @@ inline Range<const T> MakeRange(const dom::Sequence<T>& seq) { return {seq.Elements(), seq.Length()}; } -template <typename T> -inline Range<const T> MakeRange(const RawBuffer<T>& from) { - return from.Data(); -} - // - constexpr auto kUniversalAlignment = alignof(std::max_align_t); @@ -1159,13 +1167,6 @@ inline size_t ByteSize(const Range<T>& range) { // - -template <typename T> -RawBuffer<T> RawBufferView(const Range<T>& range) { - return RawBuffer<T>{range}; -} - -// - - Maybe<webgl::ErrorInfo> CheckBindBufferRange( const GLenum target, const GLuint index, const bool isBuffer, const uint64_t offset, const uint64_t size, const webgl::Limits& limits); @@ -1207,6 +1208,13 @@ inline void Memcpy(const RangedPtr<T>* const destBegin, Memcpy(destBegin, srcRange->begin(), srcRange->length()); } +template <typename Dst, typename Src> +inline void Memcpy(const Span<Dst>* const dest, const Span<Src>& src) { + MOZ_RELEASE_ASSERT(src.size_bytes() >= dest->size_bytes()); + MOZ_ASSERT(src.size_bytes() == dest->size_bytes()); + memcpy(dest->data(), src.data(), dest->size_bytes()); +} + // - inline bool StartsWith(const std::string_view str, @@ -1285,6 +1293,15 @@ inline const char* ToChars(const bool val) { return "false"; } +template <class To> +struct ReinterpretToSpan { + template <class FromT> + static inline constexpr Span<To> From(const Span<FromT>& from) { + static_assert(sizeof(FromT) == sizeof(To)); + return {reinterpret_cast<To*>(from.data()), from.size()}; + } +}; + } // namespace mozilla #endif diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list index 2cd7177b77..e1920b20ee 100644 --- a/dom/canvas/crashtests/crashtests.list +++ b/dom/canvas/crashtests/crashtests.list @@ -4,7 +4,7 @@ load 360293-1.html load 421715-1.html load 553938-1.html load 647480.html -skip-if(Android&&browserIsRemote) load 727547.html # bug 1507207 +skip-if(Android) load 727547.html # bug 1507207 load 729116.html load 745699-1.html load 746813-1.html @@ -27,13 +27,13 @@ load 1183363.html load 1190705.html load 1223740-1.html load 1225381-1.html -skip-if(azureCairo) load 1229983-1.html +load 1229983-1.html load 1229932-1.html load 1244850-1.html load 1246775-1.html load 1284356-1.html load 1284578-1.html -skip-if(d2d) load 1287515-1.html +load 1287515-1.html load 1287652-1.html load 1288872-1.html load 1290628-1.html diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 3337e97ad9..3a533d36d1 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -52,6 +52,7 @@ EXPORTS.mozilla.dom += [ "CanvasRenderingContext2D.h", "CanvasRenderingContextHelper.h", "CanvasUtils.h", + "DmdStdContainers.h", "GeneratePlaceholderCanvasData.h", "ImageBitmap.h", "ImageBitmapRenderingContext.h", diff --git a/dom/canvas/nsICanvasRenderingContextInternal.cpp b/dom/canvas/nsICanvasRenderingContextInternal.cpp index c463a00a1a..ca8e16136c 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.cpp +++ b/dom/canvas/nsICanvasRenderingContextInternal.cpp @@ -7,9 +7,12 @@ #include "mozilla/dom/CanvasUtils.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ErrorResult.h" #include "mozilla/PresShell.h" +#include "nsContentUtils.h" #include "nsPIDOMWindow.h" #include "nsRefreshDriver.h" @@ -116,3 +119,24 @@ bool nsICanvasRenderingContextInternal::ShouldResistFingerprinting( // Last resort, just check the global preference return nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget); } + +bool nsICanvasRenderingContextInternal::DispatchEvent( + const nsAString& eventName, mozilla::CanBubble aCanBubble, + mozilla::Cancelable aIsCancelable) const { + bool useDefaultHandler = true; + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), + mCanvasElement, eventName, aCanBubble, + aIsCancelable, &useDefaultHandler); + } else if (mOffscreenCanvas) { + // OffscreenCanvas case + auto event = mozilla::MakeRefPtr<mozilla::dom::Event>(mOffscreenCanvas, + nullptr, nullptr); + event->InitEvent(eventName, aCanBubble, aIsCancelable); + event->SetTrusted(true); + useDefaultHandler = mOffscreenCanvas->DispatchEvent( + *event, mozilla::dom::CallerType::System, mozilla::IgnoreErrors()); + } + return useDefaultHandler; +} diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index 580a9f07b3..deb642e9e8 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -15,6 +15,7 @@ #include "nsRFPService.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/OffscreenCanvas.h" +#include "mozilla/EventForwards.h" #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/StateWatching.h" @@ -225,6 +226,9 @@ class nsICanvasRenderingContextInternal : public nsISupports, // Checking if fingerprinting protection is enable for the given target. bool ShouldResistFingerprinting(mozilla::RFPTarget aTarget) const; + bool DispatchEvent(const nsAString& eventName, mozilla::CanBubble aCanBubble, + mozilla::Cancelable aIsCancelable) const; + protected: RefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement; RefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas; diff --git a/dom/canvas/test/mochitest.toml b/dom/canvas/test/mochitest.toml index 86cf26b632..3e96fff26b 100644 --- a/dom/canvas/test/mochitest.toml +++ b/dom/canvas/test/mochitest.toml @@ -185,6 +185,10 @@ support-files = [ ["test_ImageData_ctor.html"] +["test_accelerated_canvas_context_loss.html"] +subsuite = "gpu" +skip-if = ["verify || (os == 'win' && debug)"] + ["test_bitmaprenderer.html"] ["test_bug232227.html"] diff --git a/dom/canvas/test/reftest/colors/_generated_reftest.list b/dom/canvas/test/reftest/colors/_generated_reftest.list new file mode 100644 index 0000000000..29761e3df8 --- /dev/null +++ b/dom/canvas/test/reftest/colors/_generated_reftest.list @@ -0,0 +1,176 @@ +### Generated, do not edit. ### + ### Generated, do not edit. ### + ### Generated, do not edit. ### + +# Generated by `./generate_color_canvas_reftests.py --write`. +# - + +defaults pref(webgl.colorspaces.prototype,true) + +### Generated, do not edit. ### +# - + +# Ensure not white-screening: +!= color_canvas.html?= about:blank +# Ensure differing results with different args: +!= color_canvas.html?e_color=color(srgb,1,0,0) color_canvas.html?e_color=color(srgb,0,1,0) + +### Generated, do not edit. ### +# - +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + ### Generated, do not edit. ### +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) +skip-if(cocoaWidget) fails-if(!Android) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) +skip-if(cocoaWidget) fails == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + ### Generated, do not edit. ### +skip-if(!cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) +skip-if(cocoaWidget) == color_canvas.html?e_context=webgl&e_webgl_format=RGBA8&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"true"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=srgb&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.200,0.200) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.200,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.502,0.502) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.502,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(srgb,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(srgb,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(srgb,1.000,1.000,1.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.200,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.200,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.200,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.200,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.200,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.200,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.200) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.200) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.502,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.502,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.502,0.000,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.502,0.000,0.000) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.502,0.000) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.502,0.000) + ### Generated, do not edit. ### + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,0.000,0.000,0.502) color_canvas.html?e_context=css&e_color=color(display-p3,0.000,0.000,0.502) + == color_canvas.html?e_context=2d&e_options={"willReadFrequently":"false"}&e_cspace=display-p3&e_color=color(display-p3,1.000,1.000,1.000) color_canvas.html?e_context=css&e_color=color(display-p3,1.000,1.000,1.000) diff --git a/dom/canvas/test/reftest/colors/color_canvas.html b/dom/canvas/test/reftest/colors/color_canvas.html new file mode 100644 index 0000000000..7abbc86255 --- /dev/null +++ b/dom/canvas/test/reftest/colors/color_canvas.html @@ -0,0 +1,461 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset=utf-8> + <title>color_canvas.html</title> + </head> + <!-- +# color_canvas.html + +* Default is a 100x100 'css' canvas-equivalent div, filled with 50% srgb red. + +* We default to showing the settings pane when loaded without a query string. + This way, someone naively opens this in a browser, they can immediately see + all available options. + +* The 'Publish' button updates the url, and so causes the settings pane to + hide. + +* Clicking on the canvas toggles the settings pane for further editing. + --> + <body> + <form id=e_settings><fieldset><legend>Settings</legend> + Width: <input id=e_width type=text value=100> + <br> + Height: <input id=e_height type=text value=100> + <br> + <fieldset><legend>Canvas Context</legend> + Type: <select id=e_context> + <option value=css selected>css</option> + <option value=2d>2d</option> + <option value=webgl>webgl</option> + </select> + <br> + Options: <input id=e_options type=text value={}> + <br> + Colorspace: <input id=e_cspace type=text placeholder=srgb> + <br> + WebGL Format: <input id=e_webgl_format type=text placeholder=RGBA8> + </fieldset> + <br> + Color: <input id=e_color type=text value='color(srgb 0 0.5 0)'> + <br> + <input id=e_publish type=button value=Publish> + <input type=checkbox id=e_publish_omit_defaults checked><label for=e_publish_omit_defaults>Omit defaults</label> + <hr> + </fieldset></form> + <div id=e_canvas_list><canvas></canvas></div> + <script> +'use strict'; + +// - + +function walk_nodes_depth_first(e, fn) { + if (fn(e) === false) return; // Don't stop on `true`, or `undefined`! + for (const c of e.childNodes) { + walk_nodes_depth_first(c, fn); + } +} + +// - + +// Click the canvas to toggle the settings pane. +e_canvas_list.addEventListener('click', () => { + // Toggle display:none to hide/unhide. + e_settings.hidden = !e_settings.hidden; +}); + +// Hide settings initially if there's a query string in the url. +if (window.location.search.startsWith('?')) { + e_settings.hidden = true; +} + +// - + +// Imply .name from .id, because `new FormData` collects based on names. +walk_nodes_depth_first(e_settings, e => { + if (e.id) { + e.name = e.id; + e._default_value = e.value; + } +}); + +// - + +const URL_PARAMS = new URLSearchParams(window.location.search); +URL_PARAMS.forEach((v,k) => { + const e = window[k]; + if (!e) { + if (k) { + console.warn(`Unrecognized setting: ${k} = ${v}`); + } + return; + } + v = decode_url_v(k,v); + e.value = v; +}); + +// - + +globalThis.ASSERT = (() => { + function toPrettyString(arg) { + if (!arg) return ''+arg; + + if (arg.call) { + let s = arg.toString(); + const RE_TRIVIAL_LAMBDA = /\( *\) *=> *(.*)/; + const m = RE_TRIVIAL_LAMBDA.exec(s); + if (m) { + s = '`' + m[1] + '`'; + } + return s; + } + if (arg.constructor == Array) { + return `[${[].join.call(arg, ', ')}]`; + } + return JSON.stringify(arg); + } + + /// new AssertArg(): Construct a wrapper for args to assert functions. + function AssertArg(dict) { + this.label = Object.keys(dict)[0]; + + this.set = function(arg) { + this.arg = arg; + this.value = (arg && arg.call) ? arg.call() : arg; + this.value = toPrettyString(this.value); + }; + this.set(dict[this.label]); + + this.toString = function() { + let ret = `${this.label} ${toPrettyString(this.arg)}`; + if (this.arg.call) { + ret += ` (${this.value})`; + } + return ret; + } + } + + const eq = (a,b) => a == b; + const neq = (a,b) => a != b; + + const CMP_BY_NAME = { + '==': eq, + '!=': neq, + }; + + function IS(cmp, was, expected, _console) { + _console = _console || console; + + _console.assert(was.call, '`was.call` not defined.'); + was = new AssertArg({was}); + expected = new AssertArg({expected}); + + const fn_cmp = CMP_BY_NAME[cmp] || cmp; + + _console.assert(fn_cmp(was.value, expected.value), `${toPrettyString(was.arg)} => ${was.value} not ${cmp} ${expected}`); + if (was.value != expected.value) { + } else if (globalThis.ASSERT && globalThis.ASSERT.verbose) { + const maybe_cmp_str = (cmp == '==') ? '' : ` ${was.value} ${cmp}`; + _console.log(`${toPrettyString(was.arg)} => ${maybe_cmp_str}${expected}`); + } + } + + // - + + const MOCK_CONSOLE = { + _asserts: [], + assert: function(expr, ...args) { + if (!expr) { + this._asserts.push(args); + } + }, + log: function(...args) { + // Don't record. + }, + }; + + // - + // Test `==` + + IS('==', () => 1, 1, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => 2, 2, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => 5, () => 3, MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 1, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + IS('==', () => [1,2], () => [1,2], MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + // - + // Test `!=` + + IS('!=', () => [1,2,5], () => [1,2,3], MOCK_CONSOLE); + console.assert(MOCK_CONSOLE._asserts.length == 0, MOCK_CONSOLE._asserts); + MOCK_CONSOLE._asserts = []; + + // - + + const ret = { + verbose: false, + IS, + }; + ret.EQ = (was,expected) => ret.IS('==', was, expected); + ret.NEQ = (was,expected) => ret.IS('!=', was, expected); + ret.EEQ = (was,expected) => ret.IS('===', was, expected); + ret.NEEQ = (was,expected) => ret.IS('!==', was, expected); + ret.TRUE = was => ret.EQ(was, true); + ret.FALSE = was => ret.EQ(was, false); + ret.NULL = was => ret.EEQ(was, null); + return ret; +})(); + +// - + +function parse_css_rgb(str) { + // rgb (R ,G ,B /A ) + const m = /rgba?\(([^,]+),([^,]+),([^/)]+)(?: *\/([^)]+))?\)/.exec(str); + if (!m) throw str; + const rgba = m.slice(1,1+4).map((s,i) => { + if (s === undefined && i == 3) { + s = '1'; // Alpha defaults to 1. + } + s = s.trim(); + let v = parseFloat(s); + if (s.endsWith('%')) { + v /= 100; + } else { + if (i < 3) { // r,g,b but not a! + v /= 255; + } + } + return v; + }); + return rgba; +} +ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgb(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgba(255,255,255)'), [1,1,1,1]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60)'), () => [20/255, 40/255, 60/255, 1]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0.5)'), () => [20/255, 40/255, 60/255, 0.5]); +ASSERT.EQ(() => parse_css_rgb('rgb(20,40,60 / 0)'), () => [20/255, 40/255, 60/255, 0]); + +// - + +function parse_css_color(str) { + // color ( srgb R G B /A ) + const m = /color\( *([^ ]+) +([^ ]+) +([^ ]+) +([^/)]+)(?:\/([^)]+))?\)/.exec(str); + if (!m) { + return ['srgb', ...parse_css_rgb(str)]; + } + + const cspace = m[1].trim(); + let has_extreme_colors = false; + const rgba = m.slice(2, 2+4).map((s,i) => { + if (s === undefined && i == 3) { + s = '1'; // Alpha defaults to 1. + } + s = s.trim(); + let v = parseFloat(s); + if (s.endsWith('%')) { + v /= 100; + } + if (v < 0 || v > 1) { + has_extreme_colors = true; + } + return v; + }); + if (has_extreme_colors) { + console.warn(`parse_css_color('${str}') has colors outside [0.0,1.0]: ${JSON.stringify(rgba)}`); + } + return [cspace, ...rgba]; +} +ASSERT.EQ(() => parse_css_color('rgb(255,255,255)'), ['srgb',1,1,1,1]); +ASSERT.EQ(() => parse_css_color('rgb(20,40,60 / 0.5)'), () => ['srgb', 20/255, 40/255, 60/255, 0.5]); +ASSERT.EQ(() => parse_css_color('color(srgb 1 0 1 /0.3)'), ['srgb',1,0,1,0.3]); +ASSERT.EQ(() => parse_css_color('color(display-p3 1 0% 100%/ 30%)'), ['display-p3',1,0,1,0.3]); + +// - + +class CssColor { + constructor(cspace, r,g,b,a=1) { + this.cspace = cspace; + this.rgba = [this.r, this.g, this.b, this.a] = [r,g,b,a]; + this.rgb = this.rgba.slice(0,3); + this.tuple = [this.cspace, ...this.rgba]; + } + + toString() { + return `color(${this.cspace} ${this.rgb.join(' ')} / ${this.a})`; + } +}; +CssColor.parse = function(str) { + return new CssColor(...parse_css_color(str)); +} + +{ + let STR; + // Test round-trip. + STR = 'color(display-p3 1 0 1 / 0.3)'; + ASSERT.EQ(() => CssColor.parse(STR).toString(), STR); + + // Test round-trip normalization + ASSERT.EQ(() => CssColor.parse('color( display-p3 1 0 1/30% )').toString(), 'color(display-p3 1 0 1 / 0.3)'); +} + +// - + +function redraw() { + while (e_canvas_list.firstChild) { + e_canvas_list.removeChild(e_canvas_list.firstChild); + } + + const c = make_canvas(e_color.value.trim()); + c.style.border = '4px solid black'; + e_canvas_list.appendChild(c); +} + +function fill_canvas_rect(context /*: CanvasRenderingContext | WebGLRenderingContext*/, css_color, rect=null) { + rect = rect || {left: 0, top: 0, w: context.canvas.width, h: context.canvas.height}; + + const is_c2d = ('fillRect' in context); + if (is_c2d) { + const c2d = context; + c2d.fillStyle = css_color; + c2d.fillRect(rect.left, rect.top, rect.w, rect.h); + return; + } + + const is_webgl = ('drawArrays' in context); + if (is_webgl) { + const gl = context; + console.assert(context.canvas.width == gl.drawingBufferWidth, context.canvas.width, '!=', gl.drawingBufferWidth); + console.assert(context.canvas.height == gl.drawingBufferHeight, context.canvas.height, '!=', gl.drawingBufferHeight); + + gl.enable(gl.SCISSOR_TEST); + gl.disable(gl.DEPTH_TEST); + const bottom = rect.top + rect.h; // in y-down c2d coords + gl.scissor(rect.left, context.canvas.height - bottom, rect.w, rect.h); + + const canvas_cspace = context.drawingBufferColorSpace || 'srgb'; + if (css_color.cspace != canvas_cspace) { + console.warn(`Ignoring mismatched color vs webgl canvas cspace: ${css_color.cspace} vs ${canvas_cspace}`); + } + gl.clearColor(...css_color.rgba); + gl.clear(gl.COLOR_BUFFER_BIT); + return; + } + + console.error('Unhandled context kind:', context); +} + +window.e_canvas = null; + +function make_canvas(css_color) { + css_color = CssColor.parse(css_color); + + // `e_width` and e_friends are elements (by id) that we added to the raw HTML above. + // `e_width` is an old shorthand for `window.e_width || document.getElementById('e_width')`. + const W = parseInt(e_width.value); + const H = parseInt(e_height.value); + if (e_context.value == 'css') { + e_canvas = document.createElement('div'); + e_canvas.style.width = `${W}px`; + e_canvas.style.height = `${H}px`; + e_canvas.style.backgroundColor = css_color; + return e_canvas; + } + e_canvas = document.createElement('canvas'); + e_canvas.width = W; + e_canvas.height = H; + + let requested_options = JSON.parse(e_options.value); + requested_options.colorSpace = e_cspace.value || undefined; + + const context = e_canvas.getContext(e_context.value, requested_options); + if (requested_options.colorSpace) { + if (!context.drawingBufferColorSpace) { + console.warn(`${context.constructor.name}.drawingBufferColorSpace not supported by browser.`); + } else { + context.drawingBufferColorSpace = requested_options.colorSpace; + } + } + + if (e_webgl_format.value) { + if (!context.drawingBufferStorage) { + console.warn(`${context.constructor.name}.drawingBufferStorage not supported by browser.`); + } else { + context.drawingBufferStorage(W, H, context[e_webgl_format.value]); + } + } + + let actual_options; + if (!context.getContextAttributes) { + console.warn(`${canvas.constructor.name}.getContextAttributes not supported by browser.`); + actual_options = requested_options; + } else { + actual_options = context.getContextAttributes(); + } + + // - + + fill_canvas_rect(context, css_color); + + return e_canvas; +} + +e_settings.addEventListener('change', async () => { + redraw(); + const e_updated = document.createElement('i'); + e_updated.textContent = '(Updated!)'; + document.body.appendChild(e_updated); + await new Promise(go => setTimeout(go, 1000)); + document.body.removeChild(e_updated); +}); +redraw(); + +// - + +function encode_url_v(k,v) { + if (k == 'e_color') { + v = v.replaceAll(' ', ','); + } + console.assert(!v.includes(' '), v); + return v +} +function decode_url_v(k,v) { + console.assert(!v.includes(' '), v); + if (k == 'e_color') { + v = v.replaceAll(',', ' '); + } + return v +} +ASSERT.EQ(() => decode_url_v('e_color', encode_url_v('e_color', 'color(srgb 1 0 0)')), 'color(srgb 1 0 0)') + +e_publish.addEventListener('click', () => { + const fd = new FormData(e_settings); + let settings = []; + for (let [k,v] of fd) { + const e = window[k]; + if (e_publish_omit_defaults.checked && v == e._default_value) continue; + + v = encode_url_v(k,v); + settings.push(`${k}=${v}`); + } + settings = settings.join('&'); + if (!settings) { + settings = '='; // Empty key-value pair is 'publish with default settings' + } + window.location.search = '?' + settings; +}); + </script> + </body> +</html> diff --git a/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py new file mode 100644 index 0000000000..8c1e5f3788 --- /dev/null +++ b/dom/canvas/test/reftest/colors/generate_color_canvas_reftests.py @@ -0,0 +1,427 @@ +#! python3 + +# Typecheck: +# `pip -m mypy generate_color_canvas_reftests.py` +# +# Run: +# `./generate_color_canvas_reftests.py [--write]` + +import functools +import json +import math +import pathlib +import re +import sys +from typing import Iterable, NamedTuple, TypeVar + +ARGS = sys.argv[1:] +DEST = pathlib.Path(__file__).parent / "_generated_reftest.list" + +COL_DELIM = " " +COL_ALIGNMENT = 4 + +# - + +T = TypeVar("T") +U = TypeVar("U") + +# - + + +# crossCombine([{a:false},{a:5}], [{},{b:5}]) +# [{a:false}, {a:true}, {a:false,b:5}, {a:true,b:5}] +def cross_combine(*args_tup: list[dict]) -> list[dict]: + args = list(args_tup) + for i, a in enumerate(args): + assert type(a) == list, f"Arg{i} is {type(a)}, expected {list}." + + def cross_combine2(listA, listB): + listC = [] + for a in listA: + for b in listB: + c = dict() + c.update(a) + c.update(b) + listC.append(c) + return listC + + res: list[dict] = [dict()] + while True: + try: + next = args.pop(0) + except IndexError: + break + res = cross_combine2(res, next) + return res + + +# keyed_alternatives('count', [1,2,3]) -> [{count: 1}, {count: 2}, {count: 3}] +def keyed_alternatives(key: T, vals: Iterable[U]) -> list[dict[T, U]]: + """ + res = [] + for v in vals: + d = dict() + d[key] = v + res.append(d) + return res + """ + # return [dict([[key,v]]) for v in vals] + return [{key: v} for v in vals] + + +# - + + +def eprint(*args, **kwargs): + sys.stdout.flush() + print(*args, file=sys.stderr, **kwargs) + sys.stderr.flush() + + +# - + +# color_canvas.html?e_width=100&e_height=100&e_context=css&e_options={}&e_cspace=&e_webgl_format=&e_color=rgb(127,0,0) + +CSPACE_LIST = ["srgb", "display-p3"] +CANVAS_CSPACES = keyed_alternatives("e_cspace", CSPACE_LIST) + +RGB_LIST = [ + "0.000 0.000 0.000", + "0.200 0.200 0.200", # 0.2*255 = 51 + "0.200 0.000 0.000", + "0.000 0.200 0.000", + "0.000 0.000 0.200", + "0.502 0.502 0.502", # 0.502*255 = 128.01 + "0.502 0.000 0.000", + "0.000 0.502 0.000", + "0.000 0.000 0.502", + "1.000 1.000 1.000", + #'1.000 0.000 0.000', # These will hit gamut clipping on most displays. + #'0.000 1.000 0.000', + #'0.000 0.000 1.000', +] + +WEBGL_COLORS = keyed_alternatives("e_color", [f"color(srgb {rgb})" for rgb in RGB_LIST]) + +C2D_COLORS = [] +for cspace in CSPACE_LIST: + C2D_COLORS += keyed_alternatives( + "e_color", [f"color({cspace} {rgb})" for rgb in RGB_LIST] + ) + +# - + +WEBGL_FORMATS = keyed_alternatives( + "e_webgl_format", + [ + "RGBA8", + # Bug 1883748: (webgl.drawingbufferStorage) + #'SRGB8_ALPHA8', + #'RGBA16F', + ], +) +WEBGL = cross_combine( + [{"e_context": "webgl"}], WEBGL_FORMATS, CANVAS_CSPACES, WEBGL_COLORS +) + +# - + +C2D_OPTIONS_COMBOS = cross_combine( + keyed_alternatives("willReadFrequently", ["true", "false"]), # E.g. D2D vs Skia + # keyed_alternatives('alpha', ['true','false']) +) +C2D_OPTIONS = [ + json.dumps(config, separators=(",", ":")) for config in C2D_OPTIONS_COMBOS +] + +C2D = cross_combine( + [{"e_context": "2d"}], + keyed_alternatives("e_options", C2D_OPTIONS), + CANVAS_CSPACES, + C2D_COLORS, +) + +# - + +COMBOS: list[dict[str, str]] = cross_combine(WEBGL + C2D) + +eprint(f"{len(COMBOS)} combinations...") + +# - + +Config = dict[str, str] + + +class CssColor(NamedTuple): + cspace: str + rgb: str + + def rgb_vals(self) -> tuple[float, float, float]: + (r, g, b) = [float(z) for z in self.rgb.split(" ")] + return (r, g, b) + + def is_same_color(x, y) -> bool: + if x == y: + return True + (r, g, b) = x.rgb_vals() + if x.rgb == y.rgb and r == g and g == b: + return True + return False + + +class Reftest(NamedTuple): + notes: list[str] + op: str + test_config: Config + ref_config: Config + + +def make_ref_config(color: CssColor) -> Config: + return { + "e_context": "css", + "e_color": f"color({color.cspace} {color.rgb})", + } + + +class ColorReftest(NamedTuple): + notes: list[str] + test_config: Config + ref_color: CssColor + + def to_reftest(self): + ref_config = make_ref_config(self.ref_color) + return Reftest(self.notes.copy(), "==", self.test_config.copy(), ref_config) + + +class Expectation(NamedTuple): + notes: list[str] + test_config: Config + color: CssColor + + +def parse_css_color(s: str) -> CssColor: + m = re.match("color[(]([^)]+)[)]", s) + assert m, s + (cspace, rgb) = m.group(1).split(" ", 1) + return CssColor(cspace, rgb) + + +def correct_color_from_test_config(test_config: Config) -> CssColor: + canvas_cspace = test_config["e_cspace"] + if not canvas_cspace: + canvas_cspace = "srgb" + + correct_color = parse_css_color(test_config["e_color"]) + if test_config["e_context"] == "webgl": + # Webgl ignores the color's cspace, because webgl has no concept of + # source colorspace for clears/draws to the backbuffer. + # This (correct) behavior is as if the color's cspace were overwritten by the + # cspace of the canvas. (so expect that) + correct_color = CssColor(canvas_cspace, correct_color.rgb) + + return correct_color + + +# ------------------------------------- +# ------------------------------------- +# ------------------------------------- +# Choose (multiple?) reference configs given a test config. + + +def reftests_from_config(test_config: Config) -> Iterable[ColorReftest]: + correct_color = correct_color_from_test_config(test_config) + + if test_config["e_context"] == "2d": + # Canvas2d generally has the same behavior as css, so expect all passing. + yield ColorReftest([], test_config, correct_color) + return + + assert test_config["e_context"] == "webgl", test_config["e_context"] + + # - + + def reftests_from_expected_color( + notes: list[str], expected_color: CssColor + ) -> Iterable[ColorReftest]: + # If expecting failure, generate two tests, both expecting failure: + # 1. expected-fail test == correct_color + # 2. expected-pass test == (incorrect) expected_color + # If we fix an error, we'll see one unexpected-pass and one unexpected-fail. + # If we get a new wrong answer, we'll see one unexpected-fail. + + if not expected_color.is_same_color(correct_color): + yield ColorReftest(notes + ["fails"], test_config, correct_color) + yield ColorReftest(notes, test_config, expected_color) + else: + yield ColorReftest(notes, test_config, correct_color) + + # - + # On Mac, (with the pref) we do tag the IOSurface with the right cspace. + # On other platforms, Webgl always outputs in the display color profile + # right now. This is the same as "srgb". + + expected_color_srgb = CssColor("srgb", correct_color.rgb) + # Mac + yield from reftests_from_expected_color(["skip-if(!cocoaWidget)"], correct_color) + # Win, Lin, Android + yield from reftests_from_expected_color( + ["skip-if(cocoaWidget) "], expected_color_srgb + ) + + +# - + + +def amended_notes_from_reftest(reftest: ColorReftest) -> list[str]: + notes = reftest.notes[:] + + ref_rgb_vals = reftest.ref_color.rgb_vals() + is_green_only = ref_rgb_vals == (0, ref_rgb_vals[1], 0) + if ( + "fails" in reftest.notes + and reftest.test_config["e_context"] == "webgl" + and reftest.test_config["e_cspace"] == "display-p3" + and is_green_only + ): + # Android's display bitdepth rounds srgb green and p3 green to the same thing. + notes[notes.index("fails")] = "fails-if(!Android)" + + return notes + + +# ------------------------------------- +# ------------------------------------- +# ------------------------------------- +# Ok, back to implementation. + + +def encode_url_v(k, v): + if k == "e_color": + # reftest harness can't deal with spaces in urls, and 'color(srgb%201%200%200)' is hard to read. + v = v.replace(" ", ",") + + assert " " not in v, (k, v) + return v + + +# Cool: +assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,0,0,0)" +# Unfortunate, but tolerable: +assert encode_url_v("e_color", "color(srgb 0 0 0)") == "color(srgb,,,0,,,0,,,0)" + +# - + + +def url_from_config(kvs: Config) -> str: + parts = [f"{k}={encode_url_v(k,v)}" for k, v in kvs.items()] + url = "color_canvas.html?" + "&".join(parts) + return url + + +# - + +color_reftests: list[ColorReftest] = [] +for c in COMBOS: + color_reftests += reftests_from_config(c) +color_reftests = [ + ColorReftest(amended_notes_from_reftest(r), r.test_config, r.ref_color) + for r in color_reftests +] +reftests = [r.to_reftest() for r in color_reftests] + +# - + +HEADINGS = ["# annotations", "op", "test", "reference"] +table: list[list[str]] = [HEADINGS] +table = [ + [ + " ".join(r.notes), + r.op, + url_from_config(r.test_config), + url_from_config(r.ref_config), + ] + for r in reftests +] + +# - + + +def round_to(a, b: int) -> int: + return int(math.ceil(a / b) * b) + + +def aligned_lines_from_table( + rows: list[list[str]], col_delim=" ", col_alignment=4 +) -> Iterable[str]: + max_col_len = functools.reduce( + lambda accum, input: [max(r, len(c)) for r, c in zip(accum, input)], + rows, + [0 for _ in rows[0]], + ) + max_col_len = [round_to(x, col_alignment) for x in max_col_len] + + for i, row in enumerate(rows): + parts = [s + (" " * (col_len - len(s))) for s, col_len in zip(row, max_col_len)] + line = col_delim.join(parts) + yield line + + +# - + +GENERATED_FILE_LINE = "### Generated, do not edit. ###" + +lines = list(aligned_lines_from_table(table, COL_DELIM, COL_ALIGNMENT)) +WARN_EVERY_N_LINES = 5 +i = WARN_EVERY_N_LINES - 1 +while i < len(lines): + lines.insert(i, " " + GENERATED_FILE_LINE) + i += WARN_EVERY_N_LINES + +# - + +GENERATED_BY_ARGS = [f"./{pathlib.Path(__file__).name}"] + ARGS + +REFTEST_LIST_PREAMBLE = f"""\ +{GENERATED_FILE_LINE} + {GENERATED_FILE_LINE} + {GENERATED_FILE_LINE} + +# Generated by `{' '.join(GENERATED_BY_ARGS)}`. +# - + +defaults pref(webgl.colorspaces.prototype,true) + +{GENERATED_FILE_LINE} +# - + +# Ensure not white-screening: +!= {url_from_config({})+'='} about:blank +# Ensure differing results with different args: +!= {url_from_config({'e_color':'color(srgb 1 0 0)'})} {url_from_config({'e_color':'color(srgb 0 1 0)'})} + +{GENERATED_FILE_LINE} +# - +""" + +lines.insert(0, REFTEST_LIST_PREAMBLE) +lines.append("") + +# - + +for line in lines: + print(line) + +if "--write" not in ARGS: + eprint("Use --write to write. Exiting...") + sys.exit(0) + +# - + +eprint("Concatenating...") +file_str = "\n".join([line.rstrip() for line in lines]) + +eprint(f"Writing to {DEST}...") +DEST.write_bytes(file_str.encode()) +eprint("Done!") + +sys.exit(0) diff --git a/dom/canvas/test/reftest/filters/reftest.list b/dom/canvas/test/reftest/filters/reftest.list index cf851e2a3b..e4f21db6ce 100644 --- a/dom/canvas/test/reftest/filters/reftest.list +++ b/dom/canvas/test/reftest/filters/reftest.list @@ -1,7 +1,7 @@ == default-color.html ref.html == drop-shadow.html ref.html == drop-shadow-transformed.html ref.html -fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == global-alpha.html global-alpha-ref.html +fuzzy(0-1,0-1500) == global-alpha.html global-alpha-ref.html == global-composite-operation.html global-composite-operation-ref.html == liveness.html ref.html == liveness-document-open.html data:text/html,PASS @@ -20,7 +20,7 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||azureSkia,0-1,0-1500) == gl == units-off-screen.html ref.html fuzzy(0-2,0-700) == fillText-with-filter-opacity-1.html fillText-with-filter-opacity-1-ref.html fuzzy(0-1,0-302) == fillText-with-filter-opacity-2.html fillText-with-filter-opacity-2-ref.html -fuzzy(0-1,0-600) fuzzy-if(d2d&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html +fuzzy(0-1,0-600) fuzzy-if(winWidget&&!swgl,0-36,0-15) == strokeText-with-filter-grayscale-1.html strokeText-with-filter-grayscale-1-ref.html fuzzy(0-1,0-600) == strokeText-with-filter-grayscale-2.html strokeText-with-filter-grayscale-2-ref.html != fillText-with-shadow-1.html fillText-without-shadow-1-ref.html != fillText-with-shadow-2.html fillText-without-shadow-2-ref.html diff --git a/dom/canvas/test/reftest/reftest.list b/dom/canvas/test/reftest/reftest.list index 0eef0b3daf..26f373b32c 100644 --- a/dom/canvas/test/reftest/reftest.list +++ b/dom/canvas/test/reftest/reftest.list @@ -1,4 +1,5 @@ # Canvas Filter Reftests +include colors/_generated_reftest.list include filters/reftest.list include color_quads.list @@ -26,15 +27,6 @@ skip-if(Android) == webgl-resize-test.html wrapper.html?green.png # Check that captureStream() displays in a local video element skip-if(Android) == webgl-capturestream-test.html?preserve wrapper.html?green.png -# Some of the failure conditions are a little weird. I'm (jgilbert) setting these based on -# failures encountered when running on Try, and then targetting the Try config by -# differences in the `sandbox` contents. That is, I'm labeling based on symptoms rather -# than cause. -# WinXP R: winWidget && layersGPUAccelerated && !d3d11 -# Win7+ R: winWidget && layersGPUAccelerated && d3d11 -# Win7+ Ru: winWidget && !layersGPUAccelerated && d3d11 -# (Note that we have to remove spaces when used below) - # IMPORTANT: Expected outcomes are evaluated left-to-right, and they replace eachother. # That means that if an unconditional status (`fuzzy()`) is to the right of another status # (such as fails-if), it will overwrite the old status. @@ -233,7 +225,7 @@ fuzzy(0-1,0-150) == clip-multiple-move-2.html clip-multiple-move-2-ref.html == stroketext-shadow.html stroketext-shadow-ref.html # focus rings -fuzzy(0-1,0-2) skip-if(cocoaWidget||winWidget||gtkWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html +fuzzy(0-1,0-2) skip-if(cocoaWidget||gtkWidget||winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html # Check that captureStream() displays in a local video element == capturestream.html wrapper.html?green.png diff --git a/dom/canvas/test/test_accelerated_canvas_context_loss.html b/dom/canvas/test/test_accelerated_canvas_context_loss.html new file mode 100644 index 0000000000..6172420bcb --- /dev/null +++ b/dom/canvas/test/test_accelerated_canvas_context_loss.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Check for contextlost/restored events after GPU process restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<canvas id="c" width="512" height="512"></canvas> + +<script type="application/javascript"> + +function waitRAF() { + return new Promise((resolve, reject) => { + window.requestAnimationFrame(resolve); + }); +} + +async function restartGPUProcess() { + return await SpecialPowers.spawnChrome([], async () => { + const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + + if (gfxInfo.usingGPUProcess) { + const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" + ); + let promise = TestUtils.topicObserved("compositor-reinitialized"); + + const remoteCanvas = gfxInfo.usingRemoteCanvas; + const acceleratedCanvas = gfxInfo.usingAcceleratedCanvas; + ok(true, "Restarting GPU process, remote canvas " + remoteCanvas + ", accelerated canvas " + acceleratedCanvas); + + gfxInfo.killGPUProcessForTests(); + await promise; + + return remoteCanvas || acceleratedCanvas; + } + + ok(true, "Not using GPU process"); + return false; + }); +} + +const canvas = document.getElementById("c"); +const context = canvas.getContext("2d"); + +let restoredPromiseResolve; +let restoredPromiseReject; + +const restoredPromise = new Promise((resolve, reject) => { + restoredPromiseResolve = resolve; + restoredPromiseReject = reject; +}); + +let countLostEvents = 0; +let countRestoredEvents = 0; + +function onContextLost() { + ok(context.isContextLost(), "Canvas context should be lost during contextlost event"); + countLostEvents += 1; +} + +function onContextRestored() { + ok(!context.isContextLost(), "Canvas context should not be lost during contextrestored event"); + countRestoredEvents += 1; + + restoredPromiseResolve(true); +} + +function waitContextRestored() { + let timeoutId = window.setTimeout(restoredPromiseReject, 5000); + return restoredPromise.then(() => { + window.clearTimeout(timeoutId); + }); +} + +async function start() { + try { + canvas.addEventListener("contextlost", onContextLost); + canvas.addEventListener("contextrestored", onContextRestored); + + ok(!context.isContextLost(), "Canvas context should not be lost before initial fill"); + + context.fillStyle = 'red'; + context.fill(); + await waitRAF(); + + ok(!context.isContextLost(), "Canvas context should not be lost after initial fill"); + + const restarted = await restartGPUProcess(); + const expectedEvents = restarted ? 1 : 0; + + if (expectedEvents) { + await waitContextRestored(); + } + await waitRAF(); + + is(countLostEvents, expectedEvents, "Should have fired " + expectedEvents + " contextlost events"); + is(countRestoredEvents, expectedEvents, "Should have fired " + expectedEvents + " contextrestored events"); + ok(!context.isContextLost(), "Canvas context should not be lost after restoration"); + + context.fillStyle = 'green'; + context.fill(); + await waitRAF(); + + ok(!context.isContextLost(), "Canvas context should not be lost at completion"); + } catch (err) { + ok(false, "Caught exception " + err); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("Wait for failure condition"); +start(); + +</script> +</body> +</html> diff --git a/dom/canvas/test/webgl-conf/generated-mochitest.toml b/dom/canvas/test/webgl-conf/generated-mochitest.toml index 8a4694100d..0cee17acf4 100644 --- a/dom/canvas/test/webgl-conf/generated-mochitest.toml +++ b/dom/canvas/test/webgl-conf/generated-mochitest.toml @@ -3,7 +3,6 @@ # Mark failing (fail-if) and crashing (skip-if) tests in mochitest-errata.ini. [DEFAULT] -skip-if = ["os == 'android' && isEmulator"] prefs = "media.seamless-looping-video=false" support-files = [ @@ -5669,7 +5668,6 @@ subsuite = "webgl2-core" ["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"] subsuite = "webgl2-core" -fail-if = ["os == 'android' && android_version == '26'"] ["generated/test_2_conformance2__renderbuffers__multisampled-stencil-renderbuffer-initialization.html"] subsuite = "webgl2-core" @@ -9275,7 +9273,6 @@ subsuite = "webgl2-ext" ["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] subsuite = "webgl2-ext" fail-if = [ - "os == 'android' && android_version == '26'", "os == 'android' && !debug", "os == 'mac' && !apple_silicon", ] @@ -14501,7 +14498,6 @@ subsuite = "webgl1-ext" ["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] subsuite = "webgl1-ext" fail-if = [ - "os == 'android' && android_version == '26'", "os == 'mac' && !apple_silicon", ] @@ -16706,7 +16702,10 @@ skip-if = ["os == 'android'"] ["generated/test_conformance__textures__misc__texture-npot-video.html"] subsuite = "webgl1-core" -skip-if = ["win11_2009"] # win11 - 50/50 intermittent +skip-if = [ + "win11_2009", # win11 - 50/50 intermittent + "os == 'android' && android_version == '33'", #Bug 1873144 + ] ["generated/test_conformance__textures__misc__texture-npot.html"] subsuite = "webgl1-core" diff --git a/dom/canvas/test/webgl-conf/mochitest-errata.toml b/dom/canvas/test/webgl-conf/mochitest-errata.toml index 05bcf2c6ac..5bf2b3f89b 100644 --- a/dom/canvas/test/webgl-conf/mochitest-errata.toml +++ b/dom/canvas/test/webgl-conf/mochitest-errata.toml @@ -23,8 +23,6 @@ # * Windows 10: 10.0 [DEFAULT] -# Cross-process WebGL doesn't seem to work under an emulator -skip-if = ["os == 'android' && isEmulator"] # Bug 1799213 prefs = "media.seamless-looping-video=false" @@ -87,7 +85,10 @@ fail-if = ["os == 'mac' && !apple_silicon"] fail-if = ["os == 'android'"] ["generated/test_conformance__textures__misc__texture-npot-video.html"] -skip-if = ["win11_2009"] # win11 - 50/50 intermittent +skip-if = [ + "win11_2009", # win11 - 50/50 intermittent + "os == 'android' && android_version == '33'", #Bug 1873144 + ] #################### # Bugs surfaced during fx93 CTS update @@ -141,14 +142,12 @@ skip-if = ["os == 'linux' && debug"] ["generated/test_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] # https://bugzilla.mozilla.org/show_bug.cgi?id=1725075 fail-if = [ - "os == 'android' && android_version == '26'", "os == 'mac' && !apple_silicon", ] ["generated/test_2_conformance__glsl__bugs__vector-matrix-constructor-scalarization.html"] # Ditto fail-if = [ - "os == 'android' && android_version == '26'", "os == 'android' && !debug", "os == 'mac' && !apple_silicon", ] @@ -1102,7 +1101,6 @@ fail-if = ["os == 'win' && debug"] skip-if = ["os == 'android'"] ["generated/test_2_conformance2__renderbuffers__multisampled-renderbuffer-initialization.html"] -fail-if = ["os == 'android' && android_version == '26'"] ["generated/test_conformance__extensions__ext-sRGB.html"] fail-if = ["os == 'android'"] diff --git a/dom/canvas/test/webgl-mochitest/mochitest.toml b/dom/canvas/test/webgl-mochitest/mochitest.toml index c76e2773e0..88e2fab88b 100644 --- a/dom/canvas/test/webgl-mochitest/mochitest.toml +++ b/dom/canvas/test/webgl-mochitest/mochitest.toml @@ -35,7 +35,8 @@ fail-if = ["os == 'android'"] ["ensure-exts/test_EXT_texture_compression_bptc.html"] fail-if = [ "os == 'android'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["ensure-exts/test_EXT_texture_compression_rgtc.html"] @@ -53,36 +54,45 @@ fail-if = ["os == 'linux' && display != 'wayland'"] ["ensure-exts/test_OVR_multiview2.html"] fail-if = [ "os == 'linux'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["ensure-exts/test_WEBGL_color_buffer_float.html"] ["ensure-exts/test_WEBGL_compressed_texture_astc.html"] fail-if = [ - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_etc.html"] fail-if = [ - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_etc1.html"] fail-if = [ "os == 'linux'", - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_pvrtc.html"] fail-if = [ "os == 'android'", "os == 'linux'", - "os == 'mac'", - "os == 'win'", + "apple_catalina", + "apple_silicon", + "win10_2009", + "win11_2009", ] ["ensure-exts/test_WEBGL_compressed_texture_s3tc.html"] @@ -137,9 +147,9 @@ support-files = ["../captureStream_common.js"] ["test_has_rbab.html"] fail-if = [ - "os == 'android' && android_version == '26'", "os == 'linux'", - "os == 'mac'", + "apple_catalina", + "apple_silicon", ] ["test_hidden_alpha.html"] @@ -157,9 +167,9 @@ fail-if = [ ["test_noprog_draw.html"] ["test_pixel_pack_buffer.html"] -# skip-if = os == "win" && os_version == "10.0" # Bug 1302199 skip-if = [ - "os == 'win'", # Unofficial DXGL support regressed by bug 1632249 + "win10_2009", # Unofficial DXGL support regressed by bug 1632249 + "win11_2009", # Unofficial DXGL support regressed by bug 1632249 "apple_silicon", ] @@ -191,22 +201,25 @@ skip-if = ["win11_2009 && bits == 32"] # No fast video path for h264 decoder (do ["test_video_fastpath_theora.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] ["test_video_fastpath_vp8.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] ["test_video_fastpath_vp9.html"] skip-if = [ - "os == 'linux'", - "os == 'mac'", + "os == 'linux' && os_version == '18.04'", + "apple_catalina", + "apple_silicon", "win11_2009 && bits == 32", # No fast video path for theora decoder (done in RDD, can't be read in content) ] |