mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-25 14:17:22 +00:00
Bug 1494403 - Separate the Blob related apis. r=jrmuizel
This commit contains the Gecko-side changes from WebRender PR#3277: - Dedicated DirtyRect type. - Separate the blob image APIs from regular image ones. Differential Revision: https://phabricator.services.mozilla.com/D12463 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
68f4347fa8
commit
2fa6078f12
@ -22,6 +22,7 @@ using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebR
|
||||
using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h";
|
||||
using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
|
||||
using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
|
||||
using mozilla::wr::BlobImageKey from "mozilla/webrender/WebRenderTypes.h";
|
||||
using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
|
||||
using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h";
|
||||
using mozilla::LayoutDeviceRect from "Units.h";
|
||||
@ -117,7 +118,7 @@ struct OpAddBlobImage {
|
||||
ImageDescriptor descriptor;
|
||||
OffsetRange bytes;
|
||||
uint16_t tiling;
|
||||
ImageKey key;
|
||||
BlobImageKey key;
|
||||
};
|
||||
|
||||
struct OpUpdateImage {
|
||||
@ -129,13 +130,13 @@ struct OpUpdateImage {
|
||||
struct OpUpdateBlobImage {
|
||||
ImageDescriptor descriptor;
|
||||
OffsetRange bytes;
|
||||
ImageKey key;
|
||||
BlobImageKey key;
|
||||
ImageIntRect dirtyRect;
|
||||
};
|
||||
|
||||
struct OpSetImageVisibleArea {
|
||||
ImageIntRect area;
|
||||
ImageKey key;
|
||||
BlobImageKey key;
|
||||
};
|
||||
|
||||
struct OpUpdateExternalImage {
|
||||
@ -148,6 +149,10 @@ struct OpDeleteImage {
|
||||
ImageKey key;
|
||||
};
|
||||
|
||||
struct OpDeleteBlobImage {
|
||||
BlobImageKey key;
|
||||
};
|
||||
|
||||
struct OpAddRawFont {
|
||||
OffsetRange bytes;
|
||||
uint32_t fontIndex;
|
||||
@ -184,6 +189,7 @@ union OpUpdateResource {
|
||||
OpUpdateBlobImage;
|
||||
OpSetImageVisibleArea;
|
||||
OpDeleteImage;
|
||||
OpDeleteBlobImage;
|
||||
OpAddRawFont;
|
||||
OpAddFontDescriptor;
|
||||
OpDeleteFont;
|
||||
|
@ -300,7 +300,7 @@ IpcResourceUpdateQueue::AddImage(ImageKey key, const ImageDescriptor& aDescripto
|
||||
}
|
||||
|
||||
bool
|
||||
IpcResourceUpdateQueue::AddBlobImage(ImageKey key, const ImageDescriptor& aDescriptor,
|
||||
IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0);
|
||||
@ -344,7 +344,7 @@ IpcResourceUpdateQueue::UpdateImageBuffer(ImageKey aKey,
|
||||
}
|
||||
|
||||
bool
|
||||
IpcResourceUpdateQueue::UpdateBlobImage(ImageKey aKey,
|
||||
IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes,
|
||||
ImageIntRect aDirtyRect)
|
||||
@ -366,8 +366,8 @@ IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
|
||||
}
|
||||
|
||||
void
|
||||
IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey,
|
||||
const ImageIntRect& aArea)
|
||||
IpcResourceUpdateQueue::SetBlobImageVisibleArea(wr::BlobImageKey aKey,
|
||||
const ImageIntRect& aArea)
|
||||
{
|
||||
mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
|
||||
}
|
||||
@ -378,6 +378,12 @@ IpcResourceUpdateQueue::DeleteImage(ImageKey aKey)
|
||||
mUpdates.AppendElement(layers::OpDeleteImage(aKey));
|
||||
}
|
||||
|
||||
void
|
||||
IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey)
|
||||
{
|
||||
mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey));
|
||||
}
|
||||
|
||||
bool
|
||||
IpcResourceUpdateQueue::AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex)
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ public:
|
||||
const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes);
|
||||
|
||||
bool AddBlobImage(wr::ImageKey aKey,
|
||||
bool AddBlobImage(wr::BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes);
|
||||
|
||||
@ -106,7 +106,7 @@ public:
|
||||
const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes);
|
||||
|
||||
bool UpdateBlobImage(wr::ImageKey aKey,
|
||||
bool UpdateBlobImage(wr::BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
Range<uint8_t> aBytes,
|
||||
ImageIntRect aDirtyRect);
|
||||
@ -115,10 +115,12 @@ public:
|
||||
ImageKey aKey,
|
||||
ImageIntRect aDirtyRect);
|
||||
|
||||
void SetImageVisibleArea(ImageKey aKey, const ImageIntRect& aArea);
|
||||
void SetBlobImageVisibleArea(BlobImageKey aKey, const ImageIntRect& aArea);
|
||||
|
||||
void DeleteImage(wr::ImageKey aKey);
|
||||
|
||||
void DeleteBlobImage(wr::BlobImageKey aKey);
|
||||
|
||||
bool AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
|
||||
|
||||
bool AddFontDescriptor(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
|
||||
|
@ -461,7 +461,7 @@ WebRenderBridgeParent::UpdateResources(const nsTArray<OpUpdateResource>& aResour
|
||||
if (!reader.Read(op.bytes(), bytes)) {
|
||||
return false;
|
||||
}
|
||||
aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToDeviceIntRect(op.dirtyRect()));
|
||||
aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToLayoutIntRect(op.dirtyRect()));
|
||||
break;
|
||||
}
|
||||
case OpUpdateResource::TOpSetImageVisibleArea: {
|
||||
@ -534,6 +534,11 @@ WebRenderBridgeParent::UpdateResources(const nsTArray<OpUpdateResource>& aResour
|
||||
DeleteImage(op.key(), aUpdates);
|
||||
break;
|
||||
}
|
||||
case OpUpdateResource::TOpDeleteBlobImage: {
|
||||
const auto& op = cmd.get_OpDeleteBlobImage();
|
||||
aUpdates.DeleteBlobImage(op.key());
|
||||
break;
|
||||
}
|
||||
case OpUpdateResource::TOpDeleteFont: {
|
||||
const auto& op = cmd.get_OpDeleteFont();
|
||||
aUpdates.DeleteFont(op.key());
|
||||
|
@ -341,7 +341,7 @@ struct DIGroup
|
||||
// The current bounds of the blob image, relative to
|
||||
// the top-left of the mLayerBounds.
|
||||
IntRect mImageBounds;
|
||||
Maybe<wr::ImageKey> mKey;
|
||||
Maybe<wr::BlobImageKey> mKey;
|
||||
std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
|
||||
std::vector<RefPtr<ScaledFont>> mFonts;
|
||||
|
||||
@ -378,7 +378,7 @@ struct DIGroup
|
||||
{
|
||||
if (mKey) {
|
||||
MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty());
|
||||
aManager->AddImageKeyForDiscard(mKey.value());
|
||||
aManager->AddBlobImageKeyForDiscard(mKey.value());
|
||||
mKey = Nothing();
|
||||
}
|
||||
mFonts.clear();
|
||||
@ -634,7 +634,7 @@ struct DIGroup
|
||||
GP("Not repainting group because it's empty\n");
|
||||
GP("End EndGroup\n");
|
||||
if (mKey) {
|
||||
aResources.SetImageVisibleArea(
|
||||
aResources.SetBlobImageVisibleArea(
|
||||
mKey.value(),
|
||||
ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
|
||||
PushImage(aBuilder, bounds);
|
||||
@ -688,8 +688,8 @@ struct DIGroup
|
||||
if (!mKey) {
|
||||
if (!hasItems) // we don't want to send a new image that doesn't have any items in it
|
||||
return;
|
||||
wr::ImageKey key = aWrManager->WrBridge()->GetNextImageKey();
|
||||
GP("No previous key making new one %d\n", key.mHandle);
|
||||
wr::BlobImageKey key = wr::BlobImageKey { aWrManager->WrBridge()->GetNextImageKey() };
|
||||
GP("No previous key making new one %d\n", key._0.mHandle);
|
||||
wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
|
||||
MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
|
||||
if (!aResources.AddBlobImage(key, descriptor, bytes)) {
|
||||
@ -708,7 +708,7 @@ struct DIGroup
|
||||
}
|
||||
mFonts = std::move(fonts);
|
||||
mInvalidRect.SetEmpty();
|
||||
aResources.SetImageVisibleArea(
|
||||
aResources.SetBlobImageVisibleArea(
|
||||
mKey.value(),
|
||||
ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
|
||||
PushImage(aBuilder, bounds);
|
||||
@ -733,7 +733,7 @@ struct DIGroup
|
||||
aBuilder.SetHitTestInfo(mScrollId, hitInfo);
|
||||
aBuilder.PushImage(dest, dest, !backfaceHidden,
|
||||
wr::ToImageRendering(sampleFilter),
|
||||
mKey.value());
|
||||
wr::AsImageKey(mKey.value()));
|
||||
aBuilder.ClearHitTestInfo();
|
||||
}
|
||||
|
||||
@ -1602,13 +1602,13 @@ WebRenderCommandBuilder::PopOverrideForASR(const ActiveScrolledRoot* aASR)
|
||||
|
||||
Maybe<wr::ImageKey>
|
||||
WebRenderCommandBuilder::CreateImageKey(nsDisplayItem* aItem,
|
||||
ImageContainer* aContainer,
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
mozilla::wr::ImageRendering aRendering,
|
||||
const StackingContextHelper& aSc,
|
||||
gfx::IntSize& aSize,
|
||||
const Maybe<LayoutDeviceRect>& aAsyncImageBounds)
|
||||
ImageContainer* aContainer,
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
mozilla::wr::ImageRendering aRendering,
|
||||
const StackingContextHelper& aSc,
|
||||
gfx::IntSize& aSize,
|
||||
const Maybe<LayoutDeviceRect>& aAsyncImageBounds)
|
||||
{
|
||||
RefPtr<WebRenderImageData> imageData = CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
|
||||
MOZ_ASSERT(imageData);
|
||||
@ -1953,7 +1953,7 @@ WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
|
||||
needPaint = !invalidRegion.IsEmpty();
|
||||
}
|
||||
|
||||
if (needPaint || !fallbackData->GetKey()) {
|
||||
if (needPaint || !fallbackData->GetImageKey()) {
|
||||
nsAutoPtr<nsDisplayItemGeometry> newGeometry;
|
||||
newGeometry = aItem->AllocateGeometry(aDisplayListBuilder);
|
||||
fallbackData->SetGeometry(std::move(newGeometry));
|
||||
@ -1996,17 +1996,17 @@ WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
|
||||
|
||||
if (isInvalidated) {
|
||||
Range<uint8_t> bytes((uint8_t *)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
|
||||
wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
|
||||
wr::BlobImageKey key = wr::BlobImageKey { mManager->WrBridge()->GetNextImageKey() };
|
||||
wr::ImageDescriptor descriptor(dtSize.ToUnknownSize(), 0, dt->GetFormat(), opacity);
|
||||
if (!aResources.AddBlobImage(key, descriptor, bytes)) {
|
||||
return nullptr;
|
||||
}
|
||||
fallbackData->SetKey(key);
|
||||
fallbackData->SetBlobImageKey(key);
|
||||
fallbackData->SetFonts(fonts);
|
||||
} else {
|
||||
// If there is no invalidation region and we don't have a image key,
|
||||
// it means we don't need to push image for the item.
|
||||
if (!fallbackData->GetKey().isSome()) {
|
||||
if (!fallbackData->GetBlobImageKey().isSome()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@ -2040,7 +2040,7 @@ WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
|
||||
} else {
|
||||
// If there is no invalidation region and we don't have a image key,
|
||||
// it means we don't need to push image for the item.
|
||||
if (!fallbackData->GetKey().isSome()) {
|
||||
if (!fallbackData->GetImageKey().isSome()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@ -2061,7 +2061,7 @@ WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
|
||||
// Update current bounds to fallback data
|
||||
fallbackData->SetBounds(paintBounds);
|
||||
|
||||
MOZ_ASSERT(fallbackData->GetKey());
|
||||
MOZ_ASSERT(fallbackData->GetImageKey());
|
||||
|
||||
return fallbackData.forget();
|
||||
}
|
||||
@ -2083,7 +2083,7 @@ WebRenderCommandBuilder::BuildWrMaskImage(nsDisplayItem* aItem,
|
||||
}
|
||||
|
||||
wr::WrImageMask imageMask;
|
||||
imageMask.image = fallbackData->GetKey().value();
|
||||
imageMask.image = fallbackData->GetImageKey().value();
|
||||
imageMask.rect = wr::ToRoundedLayoutRect(imageRect);
|
||||
imageMask.repeat = false;
|
||||
return Some(imageMask);
|
||||
@ -2110,7 +2110,7 @@ WebRenderCommandBuilder::PushItemAsImage(nsDisplayItem* aItem,
|
||||
dest,
|
||||
!aItem->BackfaceIsHidden(),
|
||||
wr::ToImageRendering(sampleFilter),
|
||||
fallbackData->GetKey().value());
|
||||
fallbackData->GetImageKey().value());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ WebRenderLayerManager::DoDestroy(bool aIsSync)
|
||||
|
||||
if (WrBridge()) {
|
||||
// Just clear ImageKeys, they are deleted during WebRenderAPI destruction.
|
||||
mImageKeysToDelete.Clear();
|
||||
DiscardLocalImages();
|
||||
// CompositorAnimations are cleared by WebRenderBridgeParent.
|
||||
mDiscardedCompositorAnimationsIds.Clear();
|
||||
WrBridge()->Destroy(aIsSync);
|
||||
@ -369,11 +369,7 @@ WebRenderLayerManager::EndTransactionWithoutLayer(nsDisplayList* aDisplayList,
|
||||
mAsyncResourceUpdates.reset();
|
||||
}
|
||||
|
||||
for (const auto& key : mImageKeysToDelete) {
|
||||
resourceUpdates.DeleteImage(key);
|
||||
}
|
||||
mImageKeysToDelete.Clear();
|
||||
|
||||
DiscardImagesInTransaction(resourceUpdates);
|
||||
WrBridge()->RemoveExpiredFontKeys(resourceUpdates);
|
||||
|
||||
// Skip the synchronization for buffer since we also skip the painting during
|
||||
@ -482,14 +478,30 @@ WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
|
||||
mImageKeysToDelete.AppendElement(key);
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderLayerManager::AddBlobImageKeyForDiscard(wr::BlobImageKey key)
|
||||
{
|
||||
mBlobImageKeysToDelete.AppendElement(key);
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderLayerManager::DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResources)
|
||||
{
|
||||
for (const auto& key : mImageKeysToDelete) {
|
||||
aResources.DeleteImage(key);
|
||||
}
|
||||
for (const auto& key : mBlobImageKeysToDelete) {
|
||||
aResources.DeleteBlobImage(key);
|
||||
}
|
||||
mImageKeysToDelete.Clear();
|
||||
mBlobImageKeysToDelete.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderLayerManager::DiscardImages()
|
||||
{
|
||||
wr::IpcResourceUpdateQueue resources(WrBridge());
|
||||
for (const auto& key : mImageKeysToDelete) {
|
||||
resources.DeleteImage(key);
|
||||
}
|
||||
mImageKeysToDelete.Clear();
|
||||
DiscardImagesInTransaction(resources);
|
||||
WrBridge()->UpdateResources(resources);
|
||||
}
|
||||
|
||||
@ -532,6 +544,7 @@ WebRenderLayerManager::DiscardLocalImages()
|
||||
// This is useful in empty / failed transactions where we created
|
||||
// image keys but didn't tell the parent about them yet.
|
||||
mImageKeysToDelete.Clear();
|
||||
mBlobImageKeysToDelete.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -134,7 +134,9 @@ public:
|
||||
// adds an imagekey to a list of keys that will be discarded on the next
|
||||
// transaction or destruction
|
||||
void AddImageKeyForDiscard(wr::ImageKey);
|
||||
void AddBlobImageKeyForDiscard(wr::BlobImageKey);
|
||||
void DiscardImages();
|
||||
void DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResourceUpdates);
|
||||
void DiscardLocalImages();
|
||||
|
||||
wr::IpcResourceUpdateQueue& AsyncResourceUpdates();
|
||||
@ -189,6 +191,7 @@ private:
|
||||
private:
|
||||
nsIWidget* MOZ_NON_OWNING_REF mWidget;
|
||||
nsTArray<wr::ImageKey> mImageKeysToDelete;
|
||||
nsTArray<wr::BlobImageKey> mBlobImageKeysToDelete;
|
||||
|
||||
// Set of compositor animation ids for which there are active animations (as
|
||||
// of the last transaction) on the compositor side.
|
||||
|
@ -75,6 +75,12 @@ struct ParamTraits<mozilla::wr::ImageKey>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ParamTraits<mozilla::wr::BlobImageKey>
|
||||
: public PlainOldDataSerializer<mozilla::wr::BlobImageKey>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ParamTraits<mozilla::wr::FontKey>
|
||||
: public PlainOldDataSerializer<mozilla::wr::FontKey>
|
||||
|
@ -217,15 +217,6 @@ WebRenderImageData::UpdateImageKey(ImageContainer* aContainer,
|
||||
return mKey;
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderImageData::SetKey(const wr::ImageKey& aKey)
|
||||
{
|
||||
MOZ_ASSERT_IF(mKey, mKey.value() != aKey);
|
||||
ClearImageKey();
|
||||
mKey = Some(aKey);
|
||||
mOwnsKey = true;
|
||||
}
|
||||
|
||||
already_AddRefed<ImageClient>
|
||||
WebRenderImageData::GetImageClient()
|
||||
{
|
||||
@ -319,6 +310,35 @@ WebRenderFallbackData::SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
|
||||
mGeometry = aGeometry;
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderFallbackData::SetBlobImageKey(const wr::BlobImageKey& aKey)
|
||||
{
|
||||
ClearImageKey();
|
||||
mBlobKey = Some(aKey);
|
||||
mOwnsKey = true;
|
||||
}
|
||||
|
||||
Maybe<wr::ImageKey>
|
||||
WebRenderFallbackData::GetImageKey()
|
||||
{
|
||||
if (mBlobKey) {
|
||||
return Some(wr::AsImageKey(mBlobKey.value()));
|
||||
}
|
||||
|
||||
return mKey;
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderFallbackData::ClearImageKey()
|
||||
{
|
||||
if (mBlobKey && mOwnsKey) {
|
||||
mWRManager->AddBlobImageKeyForDiscard(mBlobKey.value());
|
||||
}
|
||||
mBlobKey.reset();
|
||||
|
||||
WebRenderImageData::ClearImageKey();
|
||||
}
|
||||
|
||||
WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
|
||||
: WebRenderUserData(aWRManager, aItem)
|
||||
{
|
||||
|
@ -116,6 +116,8 @@ struct WebRenderUserDataKey {
|
||||
|
||||
typedef nsRefPtrHashtable<nsGenericHashKey<mozilla::layers::WebRenderUserDataKey>, WebRenderUserData> WebRenderUserDataTable;
|
||||
|
||||
/// Holds some data used to share TextureClient/ImageClient with the parent
|
||||
/// process except if used with blob images (watch your step).
|
||||
class WebRenderImageData : public WebRenderUserData
|
||||
{
|
||||
public:
|
||||
@ -125,8 +127,8 @@ public:
|
||||
virtual WebRenderImageData* AsImageData() override { return this; }
|
||||
virtual UserDataType GetType() override { return UserDataType::eImage; }
|
||||
static UserDataType Type() { return UserDataType::eImage; }
|
||||
Maybe<wr::ImageKey> GetKey() { return mKey; }
|
||||
void SetKey(const wr::ImageKey& aKey);
|
||||
virtual Maybe<wr::ImageKey> GetImageKey() { return mKey; }
|
||||
void SetImageKey(const wr::ImageKey& aKey);
|
||||
already_AddRefed<ImageClient> GetImageClient();
|
||||
|
||||
Maybe<wr::ImageKey> UpdateImageKey(ImageContainer* aContainer,
|
||||
@ -154,7 +156,7 @@ public:
|
||||
bool IsAsyncAnimatedImage() const;
|
||||
|
||||
protected:
|
||||
void ClearImageKey();
|
||||
virtual void ClearImageKey();
|
||||
|
||||
RefPtr<TextureClient> mTextureOfImage;
|
||||
Maybe<wr::ImageKey> mKey;
|
||||
@ -164,6 +166,14 @@ protected:
|
||||
bool mOwnsKey;
|
||||
};
|
||||
|
||||
/// Used for fallback rendering.
|
||||
///
|
||||
/// In most cases this uses blob images but it can also render on the content side directly into
|
||||
/// a texture.
|
||||
///
|
||||
/// TODO(nical) It would be much better to separate the two use cases into separate classes and
|
||||
/// not have the blob image related code inherit from WebRenderImageData (the current code only
|
||||
/// works if we carefully use a subset of the parent code).
|
||||
class WebRenderFallbackData : public WebRenderImageData
|
||||
{
|
||||
public:
|
||||
@ -182,10 +192,16 @@ public:
|
||||
gfx::Size GetScale() { return mScale; }
|
||||
bool IsInvalid() { return mInvalid; }
|
||||
void SetFonts(const std::vector<RefPtr<gfx::ScaledFont>>& aFonts) { mFonts = aFonts; }
|
||||
Maybe<wr::BlobImageKey> GetBlobImageKey() { return mBlobKey; }
|
||||
virtual Maybe<wr::ImageKey> GetImageKey() override;
|
||||
void SetBlobImageKey(const wr::BlobImageKey& aKey);
|
||||
|
||||
RefPtr<BasicLayerManager> mBasicLayerManager;
|
||||
std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces;
|
||||
protected:
|
||||
virtual void ClearImageKey() override;
|
||||
|
||||
Maybe<wr::BlobImageKey> mBlobKey;
|
||||
nsAutoPtr<nsDisplayItemGeometry> mGeometry;
|
||||
nsRect mBounds;
|
||||
bool mInvalid;
|
||||
|
@ -323,7 +323,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
|
||||
gfx::SurfaceFormat aFormat,
|
||||
const uint16_t *aTileSize,
|
||||
const mozilla::wr::TileOffset *aTileOffset,
|
||||
const mozilla::wr::DeviceIntRect *aDirtyRect,
|
||||
const mozilla::wr::LayoutIntRect *aDirtyRect,
|
||||
Range<uint8_t> aOutput)
|
||||
{
|
||||
AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob");
|
||||
@ -486,7 +486,7 @@ bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
|
||||
mozilla::wr::ImageFormat aFormat,
|
||||
const uint16_t *aTileSize,
|
||||
const mozilla::wr::TileOffset *aTileOffset,
|
||||
const mozilla::wr::DeviceIntRect *aDirtyRect,
|
||||
const mozilla::wr::LayoutIntRect *aDirtyRect,
|
||||
mozilla::wr::MutByteSlice output)
|
||||
{
|
||||
return mozilla::wr::Moz2DRenderCallback(mozilla::wr::ByteSliceToRange(blob),
|
||||
|
@ -636,7 +636,7 @@ TransactionBuilder::AddImage(ImageKey key, const ImageDescriptor& aDescriptor,
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::AddBlobImage(ImageKey key, const ImageDescriptor& aDescriptor,
|
||||
TransactionBuilder::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes)
|
||||
{
|
||||
wr_resource_updates_add_blob_image(mTxn,
|
||||
@ -683,10 +683,10 @@ TransactionBuilder::UpdateImageBuffer(ImageKey aKey,
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::UpdateBlobImage(ImageKey aKey,
|
||||
TransactionBuilder::UpdateBlobImage(BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes,
|
||||
const wr::DeviceIntRect& aDirtyRect)
|
||||
const wr::LayoutIntRect& aDirtyRect)
|
||||
{
|
||||
wr_resource_updates_update_blob_image(mTxn,
|
||||
aKey,
|
||||
@ -728,10 +728,10 @@ TransactionBuilder::UpdateExternalImageWithDirtyRect(ImageKey aKey,
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::SetImageVisibleArea(ImageKey aKey,
|
||||
TransactionBuilder::SetImageVisibleArea(BlobImageKey aKey,
|
||||
const wr::DeviceIntRect& aArea)
|
||||
{
|
||||
wr_resource_updates_set_image_visible_area(mTxn, aKey, &aArea);
|
||||
wr_resource_updates_set_blob_image_visible_area(mTxn, aKey, &aArea);
|
||||
}
|
||||
|
||||
void
|
||||
@ -740,6 +740,12 @@ TransactionBuilder::DeleteImage(ImageKey aKey)
|
||||
wr_resource_updates_delete_image(mTxn, aKey);
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::DeleteBlobImage(BlobImageKey aKey)
|
||||
{
|
||||
wr_resource_updates_delete_blob_image(mTxn, aKey);
|
||||
}
|
||||
|
||||
void
|
||||
TransactionBuilder::AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex)
|
||||
{
|
||||
|
@ -107,7 +107,7 @@ public:
|
||||
const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes);
|
||||
|
||||
void AddBlobImage(wr::ImageKey aKey,
|
||||
void AddBlobImage(wr::BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes);
|
||||
|
||||
@ -125,10 +125,10 @@ public:
|
||||
const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes);
|
||||
|
||||
void UpdateBlobImage(wr::ImageKey aKey,
|
||||
void UpdateBlobImage(wr::BlobImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
wr::Vec<uint8_t>& aBytes,
|
||||
const wr::DeviceIntRect& aDirtyRect);
|
||||
const wr::LayoutIntRect& aDirtyRect);
|
||||
|
||||
void UpdateExternalImage(ImageKey aKey,
|
||||
const ImageDescriptor& aDescriptor,
|
||||
@ -143,10 +143,12 @@ public:
|
||||
const wr::DeviceIntRect& aDirtyRect,
|
||||
uint8_t aChannelIndex = 0);
|
||||
|
||||
void SetImageVisibleArea(ImageKey aKey, const wr::DeviceIntRect& aArea);
|
||||
void SetImageVisibleArea(BlobImageKey aKey, const wr::DeviceIntRect& aArea);
|
||||
|
||||
void DeleteImage(wr::ImageKey aKey);
|
||||
|
||||
void DeleteBlobImage(wr::BlobImageKey aKey);
|
||||
|
||||
void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
|
||||
|
||||
void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
|
||||
|
@ -363,6 +363,17 @@ static inline wr::DeviceIntRect ToDeviceIntRect(const mozilla::ImageIntRect& rec
|
||||
return r;
|
||||
}
|
||||
|
||||
// TODO: should be const LayoutDeviceIntRect instead of ImageIntRect
|
||||
static inline wr::LayoutIntRect ToLayoutIntRect(const mozilla::ImageIntRect& rect)
|
||||
{
|
||||
wr::LayoutIntRect r;
|
||||
r.origin.x = rect.X();
|
||||
r.origin.y = rect.Y();
|
||||
r.size.width = rect.Width();
|
||||
r.size.height = rect.Height();
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline wr::LayoutRect ToLayoutRect(const mozilla::LayoutDeviceIntRect& rect)
|
||||
{
|
||||
return ToLayoutRect(IntRectToRect(rect));
|
||||
|
@ -1420,14 +1420,14 @@ pub extern "C" fn wr_resource_updates_add_image(
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_resource_updates_add_blob_image(
|
||||
txn: &mut Transaction,
|
||||
image_key: WrImageKey,
|
||||
image_key: BlobImageKey,
|
||||
descriptor: &WrImageDescriptor,
|
||||
bytes: &mut WrVecU8,
|
||||
) {
|
||||
txn.add_image(
|
||||
txn.add_blob_image(
|
||||
image_key,
|
||||
descriptor.into(),
|
||||
ImageData::new_blob_image(bytes.flush_into_vec()),
|
||||
Arc::new(bytes.flush_into_vec()),
|
||||
if descriptor.format == ImageFormat::BGRA8 { Some(256) } else { None }
|
||||
);
|
||||
}
|
||||
@ -1466,17 +1466,17 @@ pub extern "C" fn wr_resource_updates_update_image(
|
||||
key,
|
||||
descriptor.into(),
|
||||
ImageData::new(bytes.flush_into_vec()),
|
||||
None
|
||||
&DirtyRect::All,
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_resource_updates_set_image_visible_area(
|
||||
pub extern "C" fn wr_resource_updates_set_blob_image_visible_area(
|
||||
txn: &mut Transaction,
|
||||
key: WrImageKey,
|
||||
key: BlobImageKey,
|
||||
area: &DeviceIntRect,
|
||||
) {
|
||||
txn.set_image_visible_area(key, *area);
|
||||
txn.set_blob_image_visible_area(key, *area);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -1498,7 +1498,7 @@ pub extern "C" fn wr_resource_updates_update_external_image(
|
||||
image_type: image_type.to_wr(),
|
||||
}
|
||||
),
|
||||
None
|
||||
&DirtyRect::All,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1522,23 +1522,23 @@ pub extern "C" fn wr_resource_updates_update_external_image_with_dirty_rect(
|
||||
image_type: image_type.to_wr(),
|
||||
}
|
||||
),
|
||||
Some(dirty_rect)
|
||||
&DirtyRect::Partial(dirty_rect)
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_resource_updates_update_blob_image(
|
||||
txn: &mut Transaction,
|
||||
image_key: WrImageKey,
|
||||
image_key: BlobImageKey,
|
||||
descriptor: &WrImageDescriptor,
|
||||
bytes: &mut WrVecU8,
|
||||
dirty_rect: DeviceIntRect,
|
||||
dirty_rect: LayoutIntRect,
|
||||
) {
|
||||
txn.update_image(
|
||||
txn.update_blob_image(
|
||||
image_key,
|
||||
descriptor.into(),
|
||||
ImageData::new_blob_image(bytes.flush_into_vec()),
|
||||
Some(dirty_rect)
|
||||
Arc::new(bytes.flush_into_vec()),
|
||||
&DirtyRect::Partial(dirty_rect)
|
||||
);
|
||||
}
|
||||
|
||||
@ -1550,6 +1550,14 @@ pub extern "C" fn wr_resource_updates_delete_image(
|
||||
txn.delete_image(key);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_resource_updates_delete_blob_image(
|
||||
txn: &mut Transaction,
|
||||
key: BlobImageKey
|
||||
) {
|
||||
txn.delete_blob_image(key);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_api_send_transaction(
|
||||
dh: &mut DocumentHandle,
|
||||
@ -2680,7 +2688,7 @@ extern "C" {
|
||||
format: ImageFormat,
|
||||
tile_size: Option<&u16>,
|
||||
tile_offset: Option<&TileOffset>,
|
||||
dirty_rect: Option<&DeviceIntRect>,
|
||||
dirty_rect: Option<&LayoutIntRect>,
|
||||
output: MutByteSlice)
|
||||
-> bool;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use std::mem;
|
||||
use std::os::raw::{c_void, c_char};
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::i32;
|
||||
use std;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -68,7 +69,7 @@ fn dump_index(blob: &[u8]) -> () {
|
||||
/// Handles the interpretation and rasterization of gecko-based (moz2d) WR blob images.
|
||||
pub struct Moz2dBlobImageHandler {
|
||||
workers: Arc<ThreadPool>,
|
||||
blob_commands: HashMap<ImageKey, BlobCommand>,
|
||||
blob_commands: HashMap<BlobImageKey, BlobCommand>,
|
||||
}
|
||||
|
||||
/// Transmute some bytes into a value.
|
||||
@ -271,12 +272,6 @@ impl Box2d {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeviceIntRect> for Box2d {
|
||||
fn from(rect: DeviceIntRect) -> Self {
|
||||
Box2d{ x1: rect.min_x(), y1: rect.min_y(), x2: rect.max_x(), y2: rect.max_y() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an API for looking up the display items in a blob image by bounds, yielding items
|
||||
/// with equal bounds in their original relative ordering.
|
||||
///
|
||||
@ -441,7 +436,7 @@ struct BlobCommand {
|
||||
request: BlobImageRequest,
|
||||
descriptor: BlobImageDescriptor,
|
||||
commands: Arc<BlobImageData>,
|
||||
dirty_rect: Option<DeviceIntRect>,
|
||||
dirty_rect: BlobDirtyRect,
|
||||
tile_size: Option<TileSize>,
|
||||
}
|
||||
|
||||
@ -450,7 +445,7 @@ struct Moz2dBlobRasterizer {
|
||||
/// Pool of rasterizers.
|
||||
workers: Arc<ThreadPool>,
|
||||
/// Blobs to rasterize.
|
||||
blob_commands: HashMap<ImageKey, BlobCommand>,
|
||||
blob_commands: HashMap<BlobImageKey, BlobCommand>,
|
||||
}
|
||||
|
||||
struct GeckoProfilerMarker {
|
||||
@ -514,30 +509,36 @@ impl AsyncBlobImageRasterizer for Moz2dBlobRasterizer {
|
||||
|
||||
fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
|
||||
let descriptor = job.descriptor;
|
||||
let buf_size = (descriptor.size.width
|
||||
* descriptor.size.height
|
||||
let buf_size = (descriptor.rect.size.width
|
||||
* descriptor.rect.size.height
|
||||
* descriptor.format.bytes_per_pixel()) as usize;
|
||||
|
||||
let mut output = vec![0u8; buf_size];
|
||||
|
||||
let dirty_rect = match job.dirty_rect {
|
||||
DirtyRect::Partial(rect) => Some(rect),
|
||||
DirtyRect::All => None,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
if wr_moz2d_render_cb(
|
||||
ByteSlice::new(&job.commands[..]),
|
||||
descriptor.size.width,
|
||||
descriptor.size.height,
|
||||
descriptor.rect.size.width,
|
||||
descriptor.rect.size.height,
|
||||
descriptor.format,
|
||||
job.tile_size.as_ref(),
|
||||
job.request.tile.as_ref(),
|
||||
job.dirty_rect.as_ref(),
|
||||
dirty_rect.as_ref(),
|
||||
MutByteSlice::new(output.as_mut_slice()),
|
||||
) {
|
||||
// We want the dirty rect local to the tile rather than the whole image.
|
||||
// TODO(nical): move that up and avoid recomupting the tile bounds in the callback
|
||||
let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
|
||||
let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
|
||||
let rasterized_rect = tx.transform_rect(&dirty_rect);
|
||||
|
||||
Ok(RasterizedBlobImage {
|
||||
rasterized_rect: job.dirty_rect.unwrap_or(
|
||||
DeviceIntRect {
|
||||
origin: DeviceIntPoint::origin(),
|
||||
size: descriptor.size,
|
||||
}
|
||||
),
|
||||
rasterized_rect,
|
||||
data: Arc::new(output),
|
||||
})
|
||||
} else {
|
||||
@ -549,7 +550,7 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
|
||||
}
|
||||
|
||||
impl BlobImageHandler for Moz2dBlobImageHandler {
|
||||
fn add(&mut self, key: ImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
|
||||
fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
|
||||
{
|
||||
let index = BlobReader::new(&data);
|
||||
assert!(index.reader.has_more());
|
||||
@ -557,18 +558,32 @@ impl BlobImageHandler for Moz2dBlobImageHandler {
|
||||
self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), tile_size });
|
||||
}
|
||||
|
||||
fn update(&mut self, key: ImageKey, data: Arc<BlobImageData>, dirty_rect: Option<DeviceIntRect>) {
|
||||
fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect) {
|
||||
match self.blob_commands.entry(key) {
|
||||
hash_map::Entry::Occupied(mut e) => {
|
||||
let command = e.get_mut();
|
||||
command.data = Arc::new(merge_blob_images(&command.data, &data,
|
||||
dirty_rect.unwrap().into()));
|
||||
let dirty_rect = if let DirtyRect::Partial(rect) = *dirty_rect {
|
||||
Box2d {
|
||||
x1: rect.min_x(),
|
||||
y1: rect.min_y(),
|
||||
x2: rect.max_x(),
|
||||
y2: rect.max_y(),
|
||||
}
|
||||
} else {
|
||||
Box2d {
|
||||
x1: i32::MIN,
|
||||
y1: i32::MIN,
|
||||
x2: i32::MAX,
|
||||
y2: i32::MAX,
|
||||
}
|
||||
};
|
||||
command.data = Arc::new(merge_blob_images(&command.data, &data, dirty_rect));
|
||||
}
|
||||
_ => { panic!("missing image key"); }
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self, key: ImageKey) {
|
||||
fn delete(&mut self, key: BlobImageKey) {
|
||||
self.blob_commands.remove(&key);
|
||||
}
|
||||
|
||||
|
@ -137,4 +137,15 @@ extern "C" {
|
||||
void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, uint64_t aTimeNs);
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace wr {
|
||||
|
||||
// Cast a blob image key into a regular image for use in
|
||||
// a display item.
|
||||
inline ImageKey AsImageKey(BlobImageKey aKey) { return aKey._0; }
|
||||
|
||||
} // namespace wr
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
#endif // WR_h
|
||||
|
@ -997,7 +997,7 @@ struct ByteSlice {
|
||||
|
||||
using TileOffset = TypedPoint2D<int32_t, TileCoordinate>;
|
||||
|
||||
using DeviceIntRect = TypedRect<int32_t, DevicePixel>;
|
||||
using LayoutIntRect = TypedRect<int32_t, LayoutPixel>;
|
||||
|
||||
struct MutByteSlice {
|
||||
uint8_t *buffer;
|
||||
@ -1073,6 +1073,17 @@ struct WrExternalImageHandler {
|
||||
}
|
||||
};
|
||||
|
||||
// An opaque identifier describing a blob image registered with WebRender.
|
||||
// This is used as a handle to reference blob images, and can be used as an
|
||||
// image in display items.
|
||||
struct BlobImageKey {
|
||||
ImageKey _0;
|
||||
|
||||
bool operator==(const BlobImageKey& aOther) const {
|
||||
return _0 == aOther._0;
|
||||
}
|
||||
};
|
||||
|
||||
struct WrImageDescriptor {
|
||||
ImageFormat format;
|
||||
int32_t width;
|
||||
@ -1089,6 +1100,8 @@ struct WrImageDescriptor {
|
||||
}
|
||||
};
|
||||
|
||||
using DeviceIntRect = TypedRect<int32_t, DevicePixel>;
|
||||
|
||||
struct WrTransformProperty {
|
||||
uint64_t id;
|
||||
LayoutTransform transform;
|
||||
@ -1619,7 +1632,7 @@ extern bool wr_moz2d_render_cb(ByteSlice aBlob,
|
||||
ImageFormat aFormat,
|
||||
const uint16_t *aTileSize,
|
||||
const TileOffset *aTileOffset,
|
||||
const DeviceIntRect *aDirtyRect,
|
||||
const LayoutIntRect *aDirtyRect,
|
||||
MutByteSlice aOutput);
|
||||
|
||||
extern void wr_notifier_external_event(WrWindowId aWindowId,
|
||||
@ -1695,7 +1708,7 @@ WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_resource_updates_add_blob_image(Transaction *aTxn,
|
||||
WrImageKey aImageKey,
|
||||
BlobImageKey aImageKey,
|
||||
const WrImageDescriptor *aDescriptor,
|
||||
WrVecU8 *aBytes)
|
||||
WR_FUNC;
|
||||
@ -1744,6 +1757,11 @@ WR_INLINE
|
||||
void wr_resource_updates_clear(Transaction *aTxn)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_resource_updates_delete_blob_image(Transaction *aTxn,
|
||||
BlobImageKey aKey)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_resource_updates_delete_font(Transaction *aTxn,
|
||||
WrFontKey aKey)
|
||||
@ -1760,17 +1778,17 @@ void wr_resource_updates_delete_image(Transaction *aTxn,
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_resource_updates_set_image_visible_area(Transaction *aTxn,
|
||||
WrImageKey aKey,
|
||||
const DeviceIntRect *aArea)
|
||||
void wr_resource_updates_set_blob_image_visible_area(Transaction *aTxn,
|
||||
BlobImageKey aKey,
|
||||
const DeviceIntRect *aArea)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
void wr_resource_updates_update_blob_image(Transaction *aTxn,
|
||||
WrImageKey aImageKey,
|
||||
BlobImageKey aImageKey,
|
||||
const WrImageDescriptor *aDescriptor,
|
||||
WrVecU8 *aBytes,
|
||||
DeviceIntRect aDirtyRect)
|
||||
LayoutIntRect aDirtyRect)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
|
Loading…
x
Reference in New Issue
Block a user