Files
tubestation/gfx/layers/wr/AsyncImagePipelineManager.cpp
Alexis Beingessner a2c8d20227 Bug 1435094 - wire up GlyphRasterSpace to nsDisplayTransform. r=kats,mstange
When a transform thinks it's animated we should abandon screen rasterization
and instead favour local rasterization. This produces a more visually
pleasant rendering, as pixel-snapping "wobbles" the text between
frames.

The float scale of GlyphRasterSpace::Local is currently unused, but this
PR tries its best to set it to a reasonable value, based on discussion
with glennw about the intended semantics. We agreed it should specify
the scale *relative* to the parent stacking context, which means it's
just whatever scaling the stacking context's transform applies. It's
possible we'll need to clamp this value or make it properly 2-dimensional
later on.

Some book-keeping is added to StackingContextHelper to ensure that
GlyphRasterSpace::Screen is never requested by a descendent
of a stacking context using GlyphRasterSpace::Local.

nsDisplayMask is changed to use a StackingContextHelper to ensure
rasterSpace is properly propagated.

In addition, this is the first commit making use of cbindgen's new support
for bridging Rust enums natively into C++! This bumps our minimum cbindgen
to 6.0.0 (just released).

MozReview-Commit-ID: 9AlsB6nUheB
2018-05-03 20:38:37 -04:00

458 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AsyncImagePipelineManager.h"
#include "CompositableHost.h"
#include "gfxEnv.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/WebRenderImageHost.h"
#include "mozilla/layers/WebRenderTextureHost.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/webrender/WebRenderTypes.h"
namespace mozilla {
namespace layers {
AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline()
: mInitialised(false)
, mIsChanged(false)
, mUseExternalImage(false)
, mFilter(wr::ImageRendering::Auto)
, mMixBlendMode(wr::MixBlendMode::Normal)
{}
AsyncImagePipelineManager::AsyncImagePipelineManager(already_AddRefed<wr::WebRenderAPI>&& aApi)
: mApi(aApi)
, mIdNamespace(mApi->GetNamespace())
, mResourceId(0)
, mAsyncImageEpoch{0}
, mWillGenerateFrame(false)
, mDestroyed(false)
{
MOZ_COUNT_CTOR(AsyncImagePipelineManager);
}
AsyncImagePipelineManager::~AsyncImagePipelineManager()
{
MOZ_COUNT_DTOR(AsyncImagePipelineManager);
}
void
AsyncImagePipelineManager::Destroy()
{
MOZ_ASSERT(!mDestroyed);
mApi = nullptr;
mDestroyed = true;
}
void
AsyncImagePipelineManager::SetWillGenerateFrame()
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
mWillGenerateFrame = true;
}
bool
AsyncImagePipelineManager::GetAndResetWillGenerateFrame()
{
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
bool ret = mWillGenerateFrame;
mWillGenerateFrame = false;
return ret;
}
void
AsyncImagePipelineManager::AddPipeline(const wr::PipelineId& aPipelineId)
{
if (mDestroyed) {
return;
}
uint64_t id = wr::AsUint64(aPipelineId);
PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
if(holder) {
// This could happen during tab move between different windows.
// Previously removed holder could be still alive for waiting destroyed.
MOZ_ASSERT(holder->mDestroyedEpoch.isSome());
holder->mDestroyedEpoch = Nothing(); // Revive holder
return;
}
holder = new PipelineTexturesHolder();
mPipelineTexturesHolders.Put(id, holder);
}
void
AsyncImagePipelineManager::RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
{
if (mDestroyed) {
return;
}
PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
MOZ_ASSERT(holder);
if (!holder) {
return;
}
holder->mDestroyedEpoch = Some(aEpoch);
}
void
AsyncImagePipelineManager::AddAsyncImagePipeline(const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost)
{
if (mDestroyed) {
return;
}
MOZ_ASSERT(aImageHost);
uint64_t id = wr::AsUint64(aPipelineId);
MOZ_ASSERT(!mAsyncImagePipelines.Get(id));
AsyncImagePipeline* holder = new AsyncImagePipeline();
holder->mImageHost = aImageHost;
mAsyncImagePipelines.Put(id, holder);
AddPipeline(aPipelineId);
}
void
AsyncImagePipelineManager::RemoveAsyncImagePipeline(const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn)
{
if (mDestroyed) {
return;
}
uint64_t id = wr::AsUint64(aPipelineId);
if (auto entry = mAsyncImagePipelines.Lookup(id)) {
AsyncImagePipeline* holder = entry.Data();
wr::Epoch epoch = GetNextImageEpoch();
aTxn.ClearDisplayList(epoch, aPipelineId);
for (wr::ImageKey key : holder->mKeys) {
aTxn.DeleteImage(key);
}
entry.Remove();
RemovePipeline(aPipelineId, epoch);
}
}
void
AsyncImagePipelineManager::UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
const LayoutDeviceRect& aScBounds,
const gfx::Matrix4x4& aScTransform,
const gfx::MaybeIntSize& aScaleToSize,
const wr::ImageRendering& aFilter,
const wr::MixBlendMode& aMixBlendMode)
{
if (mDestroyed) {
return;
}
AsyncImagePipeline* pipeline = mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
if (!pipeline) {
return;
}
pipeline->mInitialised = true;
pipeline->Update(aScBounds,
aScTransform,
aScaleToSize,
aFilter,
aMixBlendMode);
}
Maybe<TextureHost::ResourceUpdateOp>
AsyncImagePipelineManager::UpdateImageKeys(wr::TransactionBuilder& aResources,
AsyncImagePipeline* aPipeline,
nsTArray<wr::ImageKey>& aKeys)
{
MOZ_ASSERT(aKeys.IsEmpty());
MOZ_ASSERT(aPipeline);
TextureHost* texture = aPipeline->mImageHost->GetAsTextureHostForComposite();
TextureHost* previousTexture = aPipeline->mCurrentTexture.get();
if (texture == previousTexture) {
// The texture has not changed, just reuse previous ImageKeys.
aKeys = aPipeline->mKeys;
return Nothing();
}
if (!texture) {
// We don't have a new texture, there isn't much we can do.
aKeys = aPipeline->mKeys;
return Nothing();
}
aPipeline->mCurrentTexture = texture;
WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
bool useExternalImage = !gfxEnv::EnableWebRenderRecording() && wrTexture;
aPipeline->mUseExternalImage = useExternalImage;
// The non-external image code path falls back to converting the texture into
// an rgb image.
auto numKeys = useExternalImage ? texture->NumSubTextures() : 1;
// If we already had a texture and the format hasn't changed, better to reuse the image keys
// than create new ones.
bool canUpdate = !!previousTexture
&& previousTexture->GetSize() == texture->GetSize()
&& previousTexture->GetFormat() == texture->GetFormat()
&& aPipeline->mKeys.Length() == numKeys;
if (!canUpdate) {
for (auto key : aPipeline->mKeys) {
aResources.DeleteImage(key);
}
aPipeline->mKeys.Clear();
for (uint32_t i = 0; i < numKeys; ++i) {
aPipeline->mKeys.AppendElement(GenerateImageKey());
}
}
aKeys = aPipeline->mKeys;
auto op = canUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE;
if (!useExternalImage) {
return UpdateWithoutExternalImage(aResources, texture, aKeys[0], op);
}
Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length());
wrTexture->PushResourceUpdates(aResources, op, keys, wrTexture->GetExternalImageKey());
return Some(op);
}
Maybe<TextureHost::ResourceUpdateOp>
AsyncImagePipelineManager::UpdateWithoutExternalImage(wr::TransactionBuilder& aResources,
TextureHost* aTexture,
wr::ImageKey aKey,
TextureHost::ResourceUpdateOp aOp)
{
MOZ_ASSERT(aTexture);
RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface();
if (!dSurf) {
NS_ERROR("TextureHost does not return DataSourceSurface");
return Nothing();
}
gfx::DataSourceSurface::MappedSurface map;
if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
NS_ERROR("DataSourceSurface failed to map");
return Nothing();
}
gfx::IntSize size = dSurf->GetSize();
wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
// Costly copy right here...
wr::Vec<uint8_t> bytes;
bytes.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride));
if (aOp == TextureHost::UPDATE_IMAGE) {
aResources.UpdateImageBuffer(aKey, descriptor, bytes);
} else {
aResources.AddImage(aKey, descriptor, bytes);
}
dSurf->Unmap();
return Some(aOp);
}
void
AsyncImagePipelineManager::ApplyAsyncImages()
{
if (mDestroyed || mAsyncImagePipelines.Count() == 0) {
return;
}
wr::Epoch epoch = GetNextImageEpoch();
// TODO: We can improve upon this by using two transactions: one for everything that
// doesn't change the display list (in other words does not cause the scene to be
// re-built), and one for the rest. This way, if an async pipeline needs to re-build
// its display list, other async pipelines can still be rendered while the scene is
// building.
wr::TransactionBuilder txn;
// We use a pipeline with a very small display list for each video element.
// Update each of them if needed.
for (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) {
wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
AsyncImagePipeline* pipeline = iter.Data();
nsTArray<wr::ImageKey> keys;
auto op = UpdateImageKeys(txn, pipeline, keys);
bool updateDisplayList = pipeline->mInitialised &&
(pipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) &&
!!pipeline->mCurrentTexture;
// Request to generate frame if there is an update.
if (updateDisplayList || !op.isNothing()) {
SetWillGenerateFrame();
}
if (!updateDisplayList) {
// We don't need to update the display list, either because we can't or because
// the previous one is still up to date.
// We may, however, have updated some resources.
txn.UpdateEpoch(pipelineId, epoch);
if (pipeline->mCurrentTexture) {
HoldExternalImage(pipelineId, epoch, pipeline->mCurrentTexture->AsWebRenderTextureHost());
}
continue;
}
pipeline->mIsChanged = false;
wr::LayoutSize contentSize { pipeline->mScBounds.Width(), pipeline->mScBounds.Height() };
wr::DisplayListBuilder builder(pipelineId, contentSize);
MOZ_ASSERT(!keys.IsEmpty());
MOZ_ASSERT(pipeline->mCurrentTexture.get());
float opacity = 1.0f;
builder.PushStackingContext(wr::ToLayoutRect(pipeline->mScBounds),
nullptr,
nullptr,
&opacity,
pipeline->mScTransform.IsIdentity() ? nullptr : &pipeline->mScTransform,
wr::TransformStyle::Flat,
nullptr,
pipeline->mMixBlendMode,
nsTArray<wr::WrFilterOp>(),
true,
// This is fine to do unconditionally because we only push images here.
wr::GlyphRasterSpace::Screen());
LayoutDeviceRect rect(0, 0, pipeline->mCurrentTexture->GetSize().width, pipeline->mCurrentTexture->GetSize().height);
if (pipeline->mScaleToSize.isSome()) {
rect = LayoutDeviceRect(0, 0, pipeline->mScaleToSize.value().width, pipeline->mScaleToSize.value().height);
}
if (pipeline->mUseExternalImage) {
MOZ_ASSERT(pipeline->mCurrentTexture->AsWebRenderTextureHost());
Range<wr::ImageKey> range_keys(&keys[0], keys.Length());
pipeline->mCurrentTexture->PushDisplayItems(builder,
wr::ToLayoutRect(rect),
wr::ToLayoutRect(rect),
pipeline->mFilter,
range_keys);
HoldExternalImage(pipelineId, epoch, pipeline->mCurrentTexture->AsWebRenderTextureHost());
} else {
MOZ_ASSERT(keys.Length() == 1);
builder.PushImage(wr::ToLayoutRect(rect),
wr::ToLayoutRect(rect),
true,
pipeline->mFilter,
keys[0]);
}
builder.PopStackingContext();
wr::BuiltDisplayList dl;
wr::LayoutSize builderContentSize;
builder.Finalize(builderContentSize, dl);
txn.SetDisplayList(gfx::Color(0.f, 0.f, 0.f, 0.f),
epoch,
LayerSize(pipeline->mScBounds.Width(), pipeline->mScBounds.Height()),
pipelineId, builderContentSize,
dl.dl_desc, dl.dl);
}
mApi->SendTransaction(txn);
}
void
AsyncImagePipelineManager::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture)
{
if (mDestroyed) {
return;
}
MOZ_ASSERT(aTexture);
PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
MOZ_ASSERT(holder);
if (!holder) {
return;
}
// Hold WebRenderTextureHost until end of its usage on RenderThread
holder->mTextureHosts.push(ForwardingTextureHost(aEpoch, aTexture));
}
void
AsyncImagePipelineManager::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, const wr::ExternalImageId& aImageId)
{
if (mDestroyed) {
SharedSurfacesParent::Release(aImageId);
return;
}
PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
MOZ_ASSERT(holder);
if (!holder) {
SharedSurfacesParent::Release(aImageId);
return;
}
holder->mExternalImages.push(ForwardingExternalImage(aEpoch, aImageId));
}
void
AsyncImagePipelineManager::PipelineRendered(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
{
if (mDestroyed) {
return;
}
if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) {
PipelineTexturesHolder* holder = entry.Data();
// Release TextureHosts based on Epoch
while (!holder->mTextureHosts.empty()) {
if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
break;
}
holder->mTextureHosts.pop();
}
while (!holder->mExternalImages.empty()) {
if (aEpoch <= holder->mExternalImages.front().mEpoch) {
break;
}
DebugOnly<bool> released =
SharedSurfacesParent::Release(holder->mExternalImages.front().mImageId);
MOZ_ASSERT(released);
holder->mExternalImages.pop();
}
}
}
void
AsyncImagePipelineManager::PipelineRemoved(const wr::PipelineId& aPipelineId)
{
if (mDestroyed) {
return;
}
if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) {
if (entry.Data()->mDestroyedEpoch.isSome()) {
// Remove Pipeline
entry.Remove();
}
// If mDestroyedEpoch contains nothing it means we reused the same pipeline id (probably because
// we moved the tab to another window). In this case we need to keep the holder.
}
}
wr::Epoch
AsyncImagePipelineManager::GetNextImageEpoch()
{
mAsyncImageEpoch.mHandle++;
return mAsyncImageEpoch;
}
} // namespace layers
} // namespace mozilla