/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BlobSurfaceProvider.h" #include "AutoRestoreSVGState.h" #include "ImageRegion.h" #include "SVGDocumentWrapper.h" #include "mozilla/PresShell.h" #include "mozilla/dom/Document.h" #include "mozilla/gfx/2D.h" #include "mozilla/layers/IpcResourceUpdateQueue.h" #include "mozilla/layers/WebRenderBridgeChild.h" #include "mozilla/layers/WebRenderDrawEventRecorder.h" using namespace mozilla::gfx; using namespace mozilla::layers; namespace mozilla::image { BlobSurfaceProvider::BlobSurfaceProvider( const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, image::SVGDocumentWrapper* aSVGDocumentWrapper, uint32_t aImageFlags) : ISurfaceProvider(aImageKey, aSurfaceKey, AvailabilityState::StartAvailable()), mSVGDocumentWrapper(aSVGDocumentWrapper), mImageFlags(aImageFlags) { MOZ_ASSERT(mSVGDocumentWrapper); MOZ_ASSERT(aImageFlags & imgIContainer::FLAG_RECORD_BLOB); } BlobSurfaceProvider::~BlobSurfaceProvider() { if (NS_IsMainThread()) { DestroyKeys(mKeys); return; } NS_ReleaseOnMainThread("SourceSurfaceBlobImage::mSVGDocumentWrapper", mSVGDocumentWrapper.forget()); NS_DispatchToMainThread( NS_NewRunnableFunction("SourceSurfaceBlobImage::DestroyKeys", [keys = std::move(mKeys)] { DestroyKeys(keys); })); } /* static */ void BlobSurfaceProvider::DestroyKeys( const AutoTArray& aKeys) { for (const auto& entry : aKeys) { if (entry.mManager->IsDestroyed()) { continue; } WebRenderBridgeChild* wrBridge = entry.mManager->WrBridge(); if (!wrBridge || !wrBridge->MatchesNamespace(entry.mBlobKey)) { continue; } entry.mManager->GetRenderRootStateManager()->AddBlobImageKeyForDiscard( entry.mBlobKey); } } nsresult BlobSurfaceProvider::UpdateKey( layers::RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) { MOZ_ASSERT(NS_IsMainThread()); layers::WebRenderLayerManager* manager = aManager->LayerManager(); MOZ_ASSERT(manager); Maybe key; auto i = mKeys.Length(); while (i > 0) { --i; BlobImageKeyData& entry = mKeys[i]; if (entry.mManager->IsDestroyed()) { mKeys.RemoveElementAt(i); } else if (entry.mManager == manager) { WebRenderBridgeChild* wrBridge = manager->WrBridge(); MOZ_ASSERT(wrBridge); bool ownsKey = wrBridge->MatchesNamespace(entry.mBlobKey); if (ownsKey && !entry.mDirty) { key.emplace(entry.mBlobKey); continue; } // Even if the manager is the same, its underlying WebRenderBridgeChild // can change state. Either our namespace differs, and our old key has // already been discarded, or the blob has changed. Either way, we need // to rerecord it. auto newEntry = RecordDrawing(manager, aResources, ownsKey ? Some(entry.mBlobKey) : Nothing()); if (!newEntry) { if (ownsKey) { aManager->AddBlobImageKeyForDiscard(entry.mBlobKey); } mKeys.RemoveElementAt(i); continue; } key.emplace(newEntry.ref().mBlobKey); entry = std::move(newEntry.ref()); MOZ_ASSERT(!entry.mDirty); } } // We didn't find an entry. Attempt to record the blob with a new key. if (!key) { auto newEntry = RecordDrawing(manager, aResources, Nothing()); if (newEntry) { key.emplace(newEntry.ref().mBlobKey); mKeys.AppendElement(std::move(newEntry.ref())); } } if (key) { aKey = wr::AsImageKey(key.value()); return NS_OK; } return NS_ERROR_FAILURE; } void BlobSurfaceProvider::InvalidateRecording() { MOZ_ASSERT(NS_IsMainThread()); auto i = mKeys.Length(); while (i > 0) { --i; BlobImageKeyData& entry = mKeys[i]; if (entry.mManager->IsDestroyed()) { mKeys.RemoveElementAt(i); } else { entry.mDirty = true; } } } Maybe BlobSurfaceProvider::RecordDrawing( WebRenderLayerManager* aManager, wr::IpcResourceUpdateQueue& aResources, Maybe aBlobKey) { MOZ_ASSERT(!aManager->IsDestroyed()); if (mSVGDocumentWrapper->IsDrawing()) { return Nothing(); } // This is either our first pass, or we have a stale key requiring us to // re-record the SVG image draw commands. auto* rootManager = aManager->GetRenderRootStateManager(); auto* wrBridge = aManager->WrBridge(); const auto& size = GetSurfaceKey().Size(); const auto& region = GetSurfaceKey().Region(); const auto& svgContext = GetSurfaceKey().SVGContext(); IntRect imageRect = region ? region->Rect() : IntRect(IntPoint(0, 0), size); IntRect imageRectOrigin = imageRect - imageRect.TopLeft(); std::vector> fonts; bool validFonts = true; RefPtr recorder = MakeAndAddRef( [&](MemStream& aStream, std::vector>& aScaledFonts) { auto count = aScaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); for (auto& scaled : aScaledFonts) { Maybe key = wrBridge->GetFontKeyForScaledFont(scaled, aResources); if (key.isNothing()) { validFonts = false; break; } BlobFont font = {key.value(), scaled}; aStream.write((const char*)&font, sizeof(font)); } fonts = std::move(aScaledFonts); }); RefPtr dummyDt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); RefPtr dt = Factory::CreateRecordingDrawTarget(recorder, dummyDt, imageRectOrigin); if (!dt || !dt->IsValid()) { return Nothing(); } bool contextPaint = svgContext.GetContextPaint(); float animTime = (GetSurfaceKey().Playback() == PlaybackType::eStatic) ? 0.0f : mSVGDocumentWrapper->GetCurrentTimeAsFloat(); IntSize viewportSize = size; if (auto cssViewportSize = svgContext.GetViewportSize()) { // XXX losing unit viewportSize.SizeTo(cssViewportSize->width, cssViewportSize->height); } { // Get (& sanity-check) the helper-doc's presShell RefPtr presShell = mSVGDocumentWrapper->GetPresShell(); MOZ_ASSERT(presShell, "GetPresShell returned null for an SVG image?"); nsPresContext* presContext = presShell->GetPresContext(); MOZ_ASSERT(presContext, "pres shell w/out pres context"); auto* doc = presShell->GetDocument(); [[maybe_unused]] nsIURI* uri = doc ? doc->GetDocumentURI() : nullptr; AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( "SVG Image recording", GRAPHICS, nsPrintfCString("(%d,%d) %dx%d from %dx%d %s", imageRect.x, imageRect.y, imageRect.width, imageRect.height, size.width, size.height, uri ? uri->GetSpecOrDefault().get() : "N/A")); AutoRestoreSVGState autoRestore(svgContext, animTime, mSVGDocumentWrapper, contextPaint); mSVGDocumentWrapper->UpdateViewportBounds(viewportSize); mSVGDocumentWrapper->FlushImageTransformInvalidation(); RefPtr ctx = gfxContext::CreateOrNull(dt); MOZ_ASSERT(ctx); // Already checked the draw target above. nsRect svgRect; auto auPerDevPixel = presContext->AppUnitsPerDevPixel(); if (size != viewportSize) { auto scaleX = double(size.width) / viewportSize.width; auto scaleY = double(size.height) / viewportSize.height; ctx->SetMatrix(Matrix::Scaling(float(scaleX), float(scaleY))); auto scaledVisibleRect = IntRectToRect(imageRect); scaledVisibleRect.Scale(float(auPerDevPixel / scaleX), float(auPerDevPixel / scaleY)); scaledVisibleRect.Round(); svgRect.SetRect( int32_t(scaledVisibleRect.x), int32_t(scaledVisibleRect.y), int32_t(scaledVisibleRect.width), int32_t(scaledVisibleRect.height)); } else { auto scaledVisibleRect(imageRect); scaledVisibleRect.Scale(auPerDevPixel); svgRect.SetRect(scaledVisibleRect.x, scaledVisibleRect.y, scaledVisibleRect.width, scaledVisibleRect.height); } RenderDocumentFlags renderDocFlags = RenderDocumentFlags::IgnoreViewportScrolling; if (!(mImageFlags & imgIContainer::FLAG_SYNC_DECODE)) { renderDocFlags |= RenderDocumentFlags::AsyncDecodeImages; } if (mImageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) { renderDocFlags |= RenderDocumentFlags::UseHighQualityScaling; } presShell->RenderDocument(svgRect, renderDocFlags, NS_RGBA(0, 0, 0, 0), // transparent ctx); } recorder->FlushItem(imageRectOrigin); recorder->Finish(); if (!validFonts) { gfxCriticalNote << "Failed serializing fonts for blob vector image"; return Nothing(); } Range bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength); wr::BlobImageKey key = aBlobKey ? aBlobKey.value() : wr::BlobImageKey{wrBridge->GetNextImageKey()}; wr::ImageDescriptor descriptor(imageRect.Size(), 0, SurfaceFormat::OS_RGBA, wr::OpacityType::HasAlphaChannel); auto visibleRect = ImageIntRect::FromUnknownRect(imageRectOrigin); if (aBlobKey) { if (!aResources.UpdateBlobImage(key, descriptor, bytes, visibleRect, visibleRect)) { return Nothing(); } } else if (!aResources.AddBlobImage(key, descriptor, bytes, visibleRect)) { return Nothing(); } std::vector> externalSurfaces; recorder->TakeExternalSurfaces(externalSurfaces); for (auto& surface : externalSurfaces) { // While we don't use the image key with the surface, because the blob image // renderer doesn't have easy access to the resource set, we still want to // ensure one is generated. That will ensure the surface remains alive until // at least the last epoch which the blob image could be used in. wr::ImageKey key = {}; DebugOnly rv = SharedSurfacesChild::Share(surface, rootManager, aResources, key); MOZ_ASSERT(rv.value != NS_ERROR_NOT_IMPLEMENTED); } return Some(BlobImageKeyData(aManager, key, std::move(fonts), std::move(externalSurfaces))); } } // namespace mozilla::image