gecko-dev/gfx/2d/DrawTargetD2D1.cpp
Kartikaya Gupta 17eea57296 Bug 1466613 - Robustify DrawTargetRecording codepaths that create new drawtargets. r=mstange
Badly-behaved consumers of DrawTargetRecording can trigger recording of
draw calls that will fail to allocate required draw targets when the
recording is replayed. This patch tries to guard against this by
detecting these situations at record-time rather than crashing at
replay-time. When such a situation is detected, it will crash (for
content processes, to catch such scenarios) or gracefully fail (for
other processes).

Differential Revision: https://phabricator.services.mozilla.com/D11527

--HG--
extra : moz-landing-system : lando
2018-11-13 10:39:02 +00:00

2113 lines
70 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 <initguid.h>
#include "DrawTargetD2D1.h"
#include "FilterNodeSoftware.h"
#include "GradientStopsD2D.h"
#include "SourceSurfaceCapture.h"
#include "SourceSurfaceD2D1.h"
#include "SourceSurfaceDual.h"
#include "RadialGradientEffectD2D1.h"
#include "HelpersD2D.h"
#include "FilterNodeD2D1.h"
#include "ExtendInputEffectD2D1.h"
#include "nsAppRunner.h"
#include "MainThreadUtils.h"
#include "mozilla/Mutex.h"
using namespace std;
// decltype is not usable for overloaded functions.
typedef HRESULT (WINAPI*D2D1CreateFactoryFunc)(
D2D1_FACTORY_TYPE factoryType,
REFIID iid,
CONST D2D1_FACTORY_OPTIONS *pFactoryOptions,
void **factory
);
namespace mozilla {
namespace gfx {
uint64_t DrawTargetD2D1::mVRAMUsageDT;
uint64_t DrawTargetD2D1::mVRAMUsageSS;
StaticRefPtr<ID2D1Factory1> DrawTargetD2D1::mFactory;
RefPtr<ID2D1Factory1> D2DFactory()
{
return DrawTargetD2D1::factory();
}
DrawTargetD2D1::DrawTargetD2D1()
: mPushedLayers(1)
, mSnapshotLock(make_shared<Mutex>("DrawTargetD2D1::mSnapshotLock"))
, mUsedCommandListsSincePurge(0)
, mTransformedGlyphsSinceLastPurge(0)
, mComplexBlendsWithListInList(0)
, mDeviceSeq(0)
{
}
DrawTargetD2D1::~DrawTargetD2D1()
{
PopAllClips();
if (mSnapshot) {
MutexAutoLock lock(*mSnapshotLock);
// We may hold the only reference. MarkIndependent will clear mSnapshot;
// keep the snapshot object alive so it doesn't get destroyed while
// MarkIndependent is running.
RefPtr<SourceSurfaceD2D1> deathGrip = mSnapshot;
// mSnapshot can be treated as independent of this DrawTarget since we know
// this DrawTarget won't change again.
deathGrip->MarkIndependent();
// mSnapshot will be cleared now.
}
if (mDC && IsDeviceContextValid()) {
// The only way mDC can be null is if Init failed, but it can happen and the
// destructor is the only place where we need to check for it since the
// DrawTarget will destroyed right after Init fails.
mDC->EndDraw();
}
{
// Until this point in the destructor it -must- still be valid for FlushInternal
// to be called on this.
StaticMutexAutoLock lock(Factory::mDTDependencyLock);
// Targets depending on us can break that dependency, since we're obviously not going to
// be modified in the future.
for (auto iter = mDependentTargets.begin();
iter != mDependentTargets.end(); iter++) {
(*iter)->mDependingOnTargets.erase(this);
}
// Our dependencies on other targets no longer matter.
for (TargetSet::iterator iter = mDependingOnTargets.begin();
iter != mDependingOnTargets.end(); iter++) {
(*iter)->mDependentTargets.erase(this);
}
}
}
already_AddRefed<SourceSurface>
DrawTargetD2D1::Snapshot()
{
MutexAutoLock lock(*mSnapshotLock);
if (mSnapshot) {
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
PopAllClips();
Flush();
mSnapshot = new SourceSurfaceD2D1(mBitmap, mDC, mFormat, mSize, this);
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
bool
DrawTargetD2D1::EnsureLuminanceEffect()
{
if (mLuminanceEffect.get()) {
return true;
}
HRESULT hr = mDC->CreateEffect(CLSID_D2D1ColorMatrix,
getter_AddRefs(mLuminanceEffect));
if (FAILED(hr)) {
gfxCriticalError() << "Failed to create luminance effect. Code: " << hexa(hr);
return false;
}
D2D1_MATRIX_5X4_F matrix = D2D1::Matrix5x4F(0, 0, 0, 0.2125f,
0, 0, 0, 0.7154f,
0, 0, 0, 0.0721f,
0, 0, 0, 0,
0, 0, 0, 0);
mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix);
mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE, D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT);
return true;
}
already_AddRefed<SourceSurface>
DrawTargetD2D1::IntoLuminanceSource(LuminanceType aLuminanceType, float aOpacity)
{
if ((aLuminanceType != LuminanceType::LUMINANCE) ||
// See bug 1372577, some race condition where we get invalid
// results with D2D in the parent process. Fallback in that case.
XRE_IsParentProcess()) {
return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
}
// Create the luminance effect
if (!EnsureLuminanceEffect()) {
return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
}
mLuminanceEffect->SetInput(0, mBitmap);
RefPtr<ID2D1Image> luminanceOutput;
mLuminanceEffect->GetOutput(getter_AddRefs(luminanceOutput));
return MakeAndAddRef<SourceSurfaceD2D1>(luminanceOutput, mDC, SurfaceFormat::B8G8R8A8, mSize);
}
// Command lists are kept around by device contexts until EndDraw is called,
// this can cause issues with memory usage (see bug 1238328). EndDraw/BeginDraw
// are expensive though, especially relatively when little work is done, so
// we try to reduce the amount of times we execute these purges.
static const uint32_t kPushedLayersBeforePurge = 25;
// Rendering glyphs with different transforms causes the glyph cache to grow
// very large (see bug 1474883) so we must call EndDraw every so often.
static const uint32_t kTransformedGlyphsBeforePurge = 1000;
void
DrawTargetD2D1::Flush()
{
FlushInternal();
}
void
DrawTargetD2D1::DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
const Rect &aSource,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aOptions)
{
PrepareForDrawing(aOptions.mCompositionOp, ColorPattern(Color()));
D2D1_RECT_F samplingBounds;
if (aSurfOptions.mSamplingBounds == SamplingBounds::BOUNDED) {
samplingBounds = D2DRect(aSource);
} else {
samplingBounds = D2D1::RectF(0, 0, Float(aSurface->GetSize().width), Float(aSurface->GetSize().height));
}
Float xScale = aDest.Width() / aSource.Width();
Float yScale = aDest.Height() / aSource.Height();
RefPtr<ID2D1ImageBrush> brush;
// Here we scale the source pattern up to the size and position where we want
// it to be.
Matrix transform;
transform.PreTranslate(aDest.X() - aSource.X() * xScale, aDest.Y() - aSource.Y() * yScale);
transform.PreScale(xScale, yScale);
RefPtr<ID2D1Image> image = GetImageForSurface(aSurface, transform, ExtendMode::CLAMP);
if (!image) {
gfxWarning() << *this << ": Unable to get D2D image for surface.";
return;
}
RefPtr<ID2D1Bitmap> bitmap;
HRESULT hr = E_FAIL;
if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
// If this is called with a DataSourceSurface it might do a partial upload
// that our DrawBitmap call doesn't support.
hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
}
if (SUCCEEDED(hr) && bitmap && aSurfOptions.mSamplingBounds == SamplingBounds::UNBOUNDED) {
mDC->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha,
D2DFilter(aSurfOptions.mSamplingFilter), D2DRect(aSource));
} else {
// This has issues ignoring the alpha channel on windows 7 with images marked opaque.
MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::B8G8R8X8);
// Bug 1275478 - D2D1 cannot draw A8 surface correctly.
MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::A8);
mDC->CreateImageBrush(image,
D2D1::ImageBrushProperties(samplingBounds,
D2D1_EXTEND_MODE_CLAMP,
D2D1_EXTEND_MODE_CLAMP,
D2DInterpolationMode(aSurfOptions.mSamplingFilter)),
D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(transform)),
getter_AddRefs(brush));
mDC->FillRectangle(D2DRect(aDest), brush);
}
FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(Color()));
}
void
DrawTargetD2D1::DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions)
{
if (aNode->GetBackendType() != FILTER_BACKEND_DIRECT2D1_1) {
gfxWarning() << *this << ": Incompatible filter passed to DrawFilter.";
return;
}
PrepareForDrawing(aOptions.mCompositionOp, ColorPattern(Color()));
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
FilterNodeD2D1* node = static_cast<FilterNodeD2D1*>(aNode);
node->WillDraw(this);
if (aOptions.mAlpha == 1.0f) {
mDC->DrawImage(node->OutputEffect(), D2DPoint(aDestPoint), D2DRect(aSourceRect));
} else {
RefPtr<ID2D1Image> image;
node->OutputEffect()->GetOutput(getter_AddRefs(image));
Matrix mat = Matrix::Translation(aDestPoint);
RefPtr<ID2D1ImageBrush> imageBrush;
mDC->CreateImageBrush(image,
D2D1::ImageBrushProperties(D2DRect(aSourceRect)),
D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)),
getter_AddRefs(imageBrush));
mDC->FillRectangle(D2D1::RectF(aDestPoint.x, aDestPoint.y,
aDestPoint.x + aSourceRect.width,
aDestPoint.y + aSourceRect.height),
imageBrush);
}
FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(Color()));
}
void
DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator)
{
MarkChanged();
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
Matrix mat;
RefPtr<ID2D1Image> image = GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false);
if (!image) {
gfxWarning() << "Couldn't get image for surface.";
return;
}
if (!mat.IsIdentity()) {
gfxDebug() << *this << ": At this point complex partial uploads are not supported for Shadow surfaces.";
return;
}
// Step 1, create the shadow effect.
RefPtr<ID2D1Effect> shadowEffect;
HRESULT hr = mDC->CreateEffect(mFormat == SurfaceFormat::A8 ? CLSID_D2D1GaussianBlur : CLSID_D2D1Shadow,
getter_AddRefs(shadowEffect));
if (FAILED(hr) || !shadowEffect) {
gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr);
return;
}
shadowEffect->SetInput(0, image);
if (mFormat == SurfaceFormat::A8) {
shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, aSigma);
shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD);
} else {
shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma);
D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a };
shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color);
}
D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aOffset);
mDC->DrawImage(shadowEffect, &shadowPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator));
if (aSurface->GetFormat() != SurfaceFormat::A8) {
D2D1_POINT_2F imgPoint = D2DPoint(aDest);
mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator));
}
}
void
DrawTargetD2D1::ClearRect(const Rect &aRect)
{
MarkChanged();
PopAllClips();
PushClipRect(aRect);
if (mTransformDirty ||
!mTransform.IsIdentity()) {
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
}
D2D1_RECT_F clipRect;
bool isPixelAligned;
if (mTransform.IsRectilinear() &&
GetDeviceSpaceClipRect(clipRect, isPixelAligned)) {
mDC->PushAxisAlignedClip(clipRect, isPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
mDC->Clear();
mDC->PopAxisAlignedClip();
PopClip();
return;
}
RefPtr<ID2D1CommandList> list;
mUsedCommandListsSincePurge++;
mDC->CreateCommandList(getter_AddRefs(list));
mDC->SetTarget(list);
IntRect addClipRect;
RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&addClipRect);
RefPtr<ID2D1SolidColorBrush> brush;
mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), getter_AddRefs(brush));
mDC->PushAxisAlignedClip(D2D1::RectF(addClipRect.X(), addClipRect.Y(), addClipRect.XMost(), addClipRect.YMost()), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
mDC->FillGeometry(geom, brush);
mDC->PopAxisAlignedClip();
mDC->SetTarget(CurrentTarget());
list->Close();
mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_DESTINATION_OUT);
PopClip();
return;
}
void
DrawTargetD2D1::MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions)
{
MarkChanged();
RefPtr<ID2D1Bitmap> bitmap;
Matrix mat = Matrix::Translation(aOffset);
RefPtr<ID2D1Image> image = GetImageForSurface(aMask, mat, ExtendMode::CLAMP, nullptr);
MOZ_ASSERT(!mat.HasNonTranslation());
aOffset.x = mat._31;
aOffset.y = mat._32;
if (!image) {
gfxWarning() << "Failed to get image for surface.";
return;
}
PrepareForDrawing(aOptions.mCompositionOp, aSource);
IntSize size = IntSize::Truncate(aMask->GetSize().width, aMask->GetSize().height);
Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height));
HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
if (!bitmap || FAILED(hr)) {
// D2D says if we have an actual ID2D1Image and not a bitmap underlying the object,
// we can't query for a bitmap. Instead, Push/PopLayer
gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces. Falling back to push/pop layer";
RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions.mAlpha);
RefPtr<ID2D1ImageBrush> maskBrush;
hr = mDC->CreateImageBrush(image,
D2D1::ImageBrushProperties(D2D1::RectF(0, 0, size.width, size.height)),
D2D1::BrushProperties(1.0f, D2D1::IdentityMatrix()),
getter_AddRefs(maskBrush));
MOZ_ASSERT(SUCCEEDED(hr));
mDC->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), nullptr,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
D2D1::IdentityMatrix(),
1.0f, maskBrush, D2D1_LAYER_OPTIONS1_NONE),
nullptr);
mDC->FillRectangle(D2DRect(dest), source);
mDC->PopLayer();
FinalizeDrawing(aOptions.mCompositionOp, aSource);
return;
} else {
// If this is a data source surface, we might have created a partial bitmap
// for this surface and only uploaded part of the mask. In that case,
// we have to fixup our sizes here.
size.width = bitmap->GetSize().width;
size.height = bitmap->GetSize().height;
dest.SetWidth(size.width);
dest.SetHeight(size.height);
}
// FillOpacityMask only works if the antialias mode is MODE_ALIASED
mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions.mAlpha);
mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, D2DRect(dest), D2DRect(maskRect));
mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
FinalizeDrawing(aOptions.mCompositionOp, aSource);
}
void
DrawTargetD2D1::CopySurface(SourceSurface *aSurface,
const IntRect &aSourceRect,
const IntPoint &aDestination)
{
MarkChanged();
PopAllClips();
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
Matrix mat = Matrix::Translation(aDestination.x - aSourceRect.X(), aDestination.y - aSourceRect.Y());
RefPtr<ID2D1Image> image = GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false);
if (!image) {
gfxWarning() << "Couldn't get image for surface.";
return;
}
if (mat.HasNonIntegerTranslation()) {
gfxDebug() << *this << ": At this point scaled partial uploads are not supported for CopySurface.";
return;
}
IntRect sourceRect = aSourceRect;
sourceRect.SetLeftEdge(sourceRect.X() + (aDestination.x - aSourceRect.X()) - mat._31);
sourceRect.SetTopEdge(sourceRect.Y() + (aDestination.y - aSourceRect.Y()) - mat._32);
RefPtr<ID2D1Bitmap> bitmap;
HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
if (SUCCEEDED(hr) && bitmap && mFormat == SurfaceFormat::A8) {
RefPtr<ID2D1SolidColorBrush> brush;
mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White),
D2D1::BrushProperties(), getter_AddRefs(brush));
mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS);
mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
return;
}
Rect srcRect(Float(sourceRect.X()), Float(sourceRect.Y()),
Float(aSourceRect.Width()), Float(aSourceRect.Height()));
Rect dstRect(Float(aDestination.x), Float(aDestination.y),
Float(aSourceRect.Width()), Float(aSourceRect.Height()));
if (SUCCEEDED(hr) && bitmap) {
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
mDC->DrawBitmap(bitmap, D2DRect(dstRect), 1.0f,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
D2DRect(srcRect));
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
return;
}
mDC->DrawImage(image, D2D1::Point2F(Float(aDestination.x), Float(aDestination.y)),
D2DRect(srcRect), D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
}
void
DrawTargetD2D1::FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
mDC->FillRectangle(D2DRect(aRect), brush);
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
mDC->DrawRectangle(D2DRect(aRect), brush, aStrokeOptions.mLineWidth, strokeStyle);
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
mDC->DrawLine(D2DPoint(aStart), D2DPoint(aEnd), brush, aStrokeOptions.mLineWidth, strokeStyle);
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aOptions)
{
if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
return;
}
const PathD2D *d2dPath = static_cast<const PathD2D*>(aPath);
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
RefPtr<ID2D1StrokeStyle> strokeStyle = CreateStrokeStyleForOptions(aStrokeOptions);
mDC->DrawGeometry(d2dPath->mGeometry, brush, aStrokeOptions.mLineWidth, strokeStyle);
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
if (!aPath || aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
gfxDebug() << *this << ": Ignoring drawing call for incompatible path.";
return;
}
const PathD2D *d2dPath = static_cast<const PathD2D*>(aPath);
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode));
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
mDC->FillGeometry(d2dPath->mGeometry, brush);
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
if (aFont->GetType() != FontType::DWRITE) {
gfxDebug() << *this << ": Ignoring drawing call for incompatible font.";
return;
}
ScaledFontDWrite *font = static_cast<ScaledFontDWrite*>(aFont);
IDWriteRenderingParams *params = font->mParams;
AntialiasMode aaMode = font->GetDefaultAAMode();
if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
aaMode = aOptions.mAntialiasMode;
}
PrepareForDrawing(aOptions.mCompositionOp, aPattern);
bool forceClearType = false;
if (!CurrentLayer().mIsOpaque && mPermitSubpixelAA &&
aOptions.mCompositionOp == CompositionOp::OP_OVER && aaMode == AntialiasMode::SUBPIXEL) {
forceClearType = true;
}
D2D1_TEXT_ANTIALIAS_MODE d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
switch (aaMode) {
case AntialiasMode::NONE:
d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
break;
case AntialiasMode::GRAY:
d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
break;
case AntialiasMode::SUBPIXEL:
d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
break;
default:
d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
}
if (d2dAAMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE &&
!CurrentLayer().mIsOpaque && !forceClearType) {
d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
}
mDC->SetTextAntialiasMode(d2dAAMode);
if (params != mTextRenderingParams) {
mDC->SetTextRenderingParams(params);
mTextRenderingParams = params;
}
RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions.mAlpha);
AutoDWriteGlyphRun autoRun;
DWriteGlyphRunFromGlyphs(aBuffer, font, &autoRun);
bool needsRepushedLayers = false;
if (forceClearType) {
D2D1_RECT_F rect;
bool isAligned;
needsRepushedLayers = CurrentLayer().mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
// If we have a complex clip in our stack and we have a transparent
// background, and subpixel AA is permitted, we need to repush our layer
// stack limited by the glyph run bounds initializing our layers for
// subpixel AA.
if (needsRepushedLayers) {
mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun,
DWRITE_MEASURING_MODE_NATURAL, &rect);
rect.left = std::floor(rect.left);
rect.right = std::ceil(rect.right);
rect.top = std::floor(rect.top);
rect.bottom = std::ceil(rect.bottom);
PopAllClips();
if (!mTransform.IsRectilinear()) {
// We must limit the pixels we touch to the -user space- bounds of
// the glyphs being drawn. In order not to get transparent pixels
// copied up in our pushed layer stack.
D2D1_RECT_F userRect;
mDC->SetTransform(D2D1::IdentityMatrix());
mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun,
DWRITE_MEASURING_MODE_NATURAL, &userRect);
RefPtr<ID2D1PathGeometry> path;
factory()->CreatePathGeometry(getter_AddRefs(path));
RefPtr<ID2D1GeometrySink> sink;
path->Open(getter_AddRefs(sink));
AddRectToSink(sink, userRect);
sink->Close();
mDC->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), path, D2D1_ANTIALIAS_MODE_ALIASED,
D2DMatrix(mTransform), 1.0f, nullptr,
D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND |
D2D1_LAYER_OPTIONS1_IGNORE_ALPHA), nullptr);
}
PushClipsToDC(mDC, true, rect);
mDC->SetTransform(D2DMatrix(mTransform));
}
}
if (brush) {
mDC->DrawGlyphRun(D2D1::Point2F(), &autoRun, brush);
}
if (mTransform.HasNonTranslation()) {
mTransformedGlyphsSinceLastPurge += aBuffer.mNumGlyphs;
}
if (needsRepushedLayers) {
PopClipsFromDC(mDC);
if (!mTransform.IsRectilinear()) {
mDC->PopLayer();
}
}
FinalizeDrawing(aOptions.mCompositionOp, aPattern);
}
void
DrawTargetD2D1::Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions)
{
PrepareForDrawing(aOptions.mCompositionOp, aSource);
RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions.mAlpha);
RefPtr<ID2D1Brush> mask = CreateBrushForPattern(aMask, 1.0f);
mDC->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
D2D1::IdentityMatrix(),
1.0f, mask),
nullptr);
Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height);
Matrix mat = mTransform;
mat.Invert();
mDC->FillRectangle(D2DRect(mat.TransformBounds(rect)), source);
mDC->PopLayer();
FinalizeDrawing(aOptions.mCompositionOp, aSource);
}
void
DrawTargetD2D1::PushClipGeometry(ID2D1Geometry* aGeometry,
const D2D1_MATRIX_3X2_F& aTransform,
bool aPixelAligned)
{
mCurrentClippedGeometry = nullptr;
PushedClip clip;
clip.mGeometry = aGeometry;
clip.mTransform = aTransform;
clip.mIsPixelAligned = aPixelAligned;
aGeometry->GetBounds(aTransform, &clip.mBounds);
CurrentLayer().mPushedClips.push_back(clip);
// The transform of clips is relative to the world matrix, since we use the total
// transform for the clips, make the world matrix identity.
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
if (CurrentLayer().mClipsArePushed) {
PushD2DLayer(mDC, clip.mGeometry, clip.mTransform, clip.mIsPixelAligned);
}
}
void
DrawTargetD2D1::PushClip(const Path *aPath)
{
if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
return;
}
RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(aPath));
PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform));
}
void
DrawTargetD2D1::PushClipRect(const Rect &aRect)
{
if (!mTransform.IsRectilinear()) {
// Whoops, this isn't a rectangle in device space, Direct2D will not deal
// with this transform the way we want it to.
// See remarks: http://msdn.microsoft.com/en-us/library/dd316860%28VS.85%29.aspx
RefPtr<ID2D1Geometry> geom = ConvertRectToGeometry(D2DRect(aRect));
return PushClipGeometry(geom, D2DMatrix(mTransform));
}
mCurrentClippedGeometry = nullptr;
PushedClip clip;
Rect rect = mTransform.TransformBounds(aRect);
IntRect intRect;
clip.mIsPixelAligned = rect.ToIntRect(&intRect);
// Do not store the transform, just store the device space rectangle directly.
clip.mBounds = D2DRect(rect);
CurrentLayer().mPushedClips.push_back(clip);
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
if (CurrentLayer().mClipsArePushed) {
mDC->PushAxisAlignedClip(clip.mBounds, clip.mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
}
void
DrawTargetD2D1::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount)
{
// Build a path for the union of the rects.
RefPtr<ID2D1PathGeometry> path;
factory()->CreatePathGeometry(getter_AddRefs(path));
RefPtr<ID2D1GeometrySink> sink;
path->Open(getter_AddRefs(sink));
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
for (uint32_t i = 0; i < aCount; i++) {
const IntRect& rect = aRects[i];
sink->BeginFigure(D2DPoint(rect.TopLeft()), D2D1_FIGURE_BEGIN_FILLED);
D2D1_POINT_2F lines[3] = { D2DPoint(rect.TopRight()), D2DPoint(rect.BottomRight()), D2DPoint(rect.BottomLeft()) };
sink->AddLines(lines, 3);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
}
sink->Close();
// The path is in device-space, so there is no transform needed,
// and all rects are pixel aligned.
PushClipGeometry(path, D2D1::IdentityMatrix(), true);
}
void
DrawTargetD2D1::PopClip()
{
mCurrentClippedGeometry = nullptr;
if (CurrentLayer().mPushedClips.empty()) {
gfxDevCrash(LogReason::UnbalancedClipStack) << "DrawTargetD2D1::PopClip: No clip to pop.";
return;
}
if (CurrentLayer().mClipsArePushed) {
if (CurrentLayer().mPushedClips.back().mGeometry) {
mDC->PopLayer();
} else {
mDC->PopAxisAlignedClip();
}
}
CurrentLayer().mPushedClips.pop_back();
}
void
DrawTargetD2D1::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform, const IntRect& aBounds,
bool aCopyBackground)
{
D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE;
if (aOpaque) {
options |= D2D1_LAYER_OPTIONS1_IGNORE_ALPHA;
}
if (aCopyBackground) {
options |= D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
}
RefPtr<ID2D1ImageBrush> mask;
Matrix maskTransform = aMaskTransform;
RefPtr<ID2D1PathGeometry> clip;
if (aMask) {
RefPtr<ID2D1Image> image = GetImageForSurface(aMask, maskTransform, ExtendMode::CLAMP);
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
// The mask is given in user space. Our layer will apply it in device space.
maskTransform = maskTransform * mTransform;
if (image) {
IntSize maskSize = aMask->GetSize();
HRESULT hr = mDC->CreateImageBrush(image,
D2D1::ImageBrushProperties(D2D1::RectF(0, 0, maskSize.width, maskSize.height)),
D2D1::BrushProperties(1.0f, D2DMatrix(maskTransform)),
getter_AddRefs(mask));
if (FAILED(hr)) {
gfxWarning() <<"[D2D1.1] Failed to create a ImageBrush, code: " << hexa(hr);
}
factory()->CreatePathGeometry(getter_AddRefs(clip));
RefPtr<ID2D1GeometrySink> sink;
clip->Open(getter_AddRefs(sink));
AddRectToSink(sink, D2D1::RectF(0, 0, aMask->GetSize().width, aMask->GetSize().height));
sink->Close();
} else {
gfxCriticalError() << "Failed to get image for mask surface!";
}
}
PushAllClips();
mDC->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), clip, D2D1_ANTIALIAS_MODE_ALIASED, D2DMatrix(maskTransform), aOpacity, mask, options), nullptr);
PushedLayer pushedLayer;
pushedLayer.mClipsArePushed = false;
pushedLayer.mIsOpaque = aOpaque;
pushedLayer.mOldPermitSubpixelAA = mPermitSubpixelAA;
mPermitSubpixelAA = aOpaque;
mDC->CreateCommandList(getter_AddRefs(pushedLayer.mCurrentList));
mPushedLayers.push_back(pushedLayer);
mDC->SetTarget(CurrentTarget());
mUsedCommandListsSincePurge++;
}
void
DrawTargetD2D1::PopLayer()
{
MOZ_ASSERT(CurrentLayer().mPushedClips.size() == 0);
RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList;
mPermitSubpixelAA = CurrentLayer().mOldPermitSubpixelAA;
mPushedLayers.pop_back();
mDC->SetTarget(CurrentTarget());
list->Close();
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
DCCommandSink sink(mDC);
list->Stream(&sink);
mComplexBlendsWithListInList = 0;
mDC->PopLayer();
}
already_AddRefed<SourceSurface>
DrawTargetD2D1::CreateSourceSurfaceFromData(unsigned char *aData,
const IntSize &aSize,
int32_t aStride,
SurfaceFormat aFormat) const
{
RefPtr<ID2D1Bitmap1> bitmap;
HRESULT hr = mDC->CreateBitmap(D2DIntSize(aSize), aData, aStride,
D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, D2DPixelFormat(aFormat)),
getter_AddRefs(bitmap));
if (FAILED(hr) || !bitmap) {
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "[D2D1.1] 1CreateBitmap failure " << aSize << " Code: " << hexa(hr) << " format " << (int)aFormat;
return nullptr;
}
return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), mDC, aFormat, aSize);
}
already_AddRefed<DrawTarget>
DrawTargetD2D1::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
RefPtr<DrawTargetD2D1> dt = new DrawTargetD2D1();
if (!dt->Init(aSize, aFormat)) {
return nullptr;
}
return dt.forget();
}
bool
DrawTargetD2D1::CanCreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
return (mDC->GetMaximumBitmapSize() >= UINT32(aSize.width) &&
mDC->GetMaximumBitmapSize() >= UINT32(aSize.height));
}
already_AddRefed<PathBuilder>
DrawTargetD2D1::CreatePathBuilder(FillRule aFillRule) const
{
RefPtr<ID2D1PathGeometry> path;
HRESULT hr = factory()->CreatePathGeometry(getter_AddRefs(path));
if (FAILED(hr)) {
gfxWarning() << *this << ": Failed to create Direct2D Path Geometry. Code: " << hexa(hr);
return nullptr;
}
RefPtr<ID2D1GeometrySink> sink;
hr = path->Open(getter_AddRefs(sink));
if (FAILED(hr)) {
gfxWarning() << *this << ": Failed to access Direct2D Path Geometry. Code: " << hexa(hr);
return nullptr;
}
if (aFillRule == FillRule::FILL_WINDING) {
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
}
return MakeAndAddRef<PathBuilderD2D>(sink, path, aFillRule, BackendType::DIRECT2D1_1);
}
already_AddRefed<GradientStops>
DrawTargetD2D1::CreateGradientStops(GradientStop *rawStops, uint32_t aNumStops, ExtendMode aExtendMode) const
{
if (aNumStops == 0) {
gfxWarning() << *this << ": Failed to create GradientStopCollection with no stops.";
return nullptr;
}
D2D1_GRADIENT_STOP *stops = new D2D1_GRADIENT_STOP[aNumStops];
for (uint32_t i = 0; i < aNumStops; i++) {
stops[i].position = rawStops[i].offset;
stops[i].color = D2DColor(rawStops[i].color);
}
RefPtr<ID2D1GradientStopCollection1> stopCollection;
HRESULT hr =
mDC->CreateGradientStopCollection(stops, aNumStops,
D2D1_COLOR_SPACE_SRGB, D2D1_COLOR_SPACE_SRGB,
D2D1_BUFFER_PRECISION_8BPC_UNORM, D2DExtend(aExtendMode, Axis::BOTH),
D2D1_COLOR_INTERPOLATION_MODE_PREMULTIPLIED,
getter_AddRefs(stopCollection));
delete [] stops;
if (FAILED(hr)) {
gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: " << hexa(hr);
return nullptr;
}
RefPtr<ID3D11Device> device = Factory::GetDirect3D11Device();
return MakeAndAddRef<GradientStopsD2D>(stopCollection, device);
}
already_AddRefed<FilterNode>
DrawTargetD2D1::CreateFilter(FilterType aType)
{
return FilterNodeD2D1::Create(mDC, aType);
}
void
DrawTargetD2D1::GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices,
uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics)
{
MOZ_ASSERT(aScaledFont->GetType() == FontType::DWRITE);
aScaledFont->GetGlyphDesignMetrics(aGlyphIndices, aNumGlyphs, aGlyphMetrics);
// GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to
// account for antialiasing.
for (uint32_t i = 0; i < aNumGlyphs; i++) {
if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
aGlyphMetrics[i].mWidth += 2.0f;
aGlyphMetrics[i].mXBearing -= 1.0f;
}
}
}
bool
DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat)
{
HRESULT hr;
RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
if (!device) {
gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat).";
return false;
}
hr = device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, getter_AddRefs(mDC));
if (FAILED(hr)) {
gfxCriticalError() <<"[D2D1.1] 1Failed to create a DeviceContext, code: " << hexa(hr) << " format " << (int)aFormat;
return false;
}
RefPtr<IDXGISurface> dxgiSurface;
aTexture->QueryInterface(__uuidof(IDXGISurface),
(void**)((IDXGISurface**)getter_AddRefs(dxgiSurface)));
if (!dxgiSurface) {
gfxCriticalError() <<"[D2D1.1] Failed to obtain a DXGI surface.";
return false;
}
D2D1_BITMAP_PROPERTIES1 props;
props.dpiX = 96;
props.dpiY = 96;
props.pixelFormat = D2DPixelFormat(aFormat);
props.colorContext = nullptr;
props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
hr = mDC->CreateBitmapFromDxgiSurface(dxgiSurface, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1.1] CreateBitmapFromDxgiSurface failure Code: " << hexa(hr) << " format " << (int)aFormat;
return false;
}
mFormat = aFormat;
D3D11_TEXTURE2D_DESC desc;
aTexture->GetDesc(&desc);
mSize.width = desc.Width;
mSize.height = desc.Height;
// This single solid color brush system is not very 'threadsafe', however,
// issueing multiple drawing commands simultaneously to a single drawtarget
// from multiple threads is unexpected since there's no way to guarantee
// ordering in that situation anyway.
hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0), getter_AddRefs(mSolidColorBrush));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1.1] Failure creating solid color brush (I1).";
return false;
}
mDC->SetTarget(CurrentTarget());
mDC->BeginDraw();
CurrentLayer().mIsOpaque = aFormat == SurfaceFormat::B8G8R8X8;
return true;
}
bool
DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat)
{
HRESULT hr;
RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
if (!device) {
gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(IntSize, SurfaceFormat).";
return false;
}
hr = device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, getter_AddRefs(mDC));
if (FAILED(hr)) {
gfxCriticalError() <<"[D2D1.1] 2Failed to create a DeviceContext, code: " << hexa(hr) << " format " << (int)aFormat;
return false;
}
if (mDC->GetMaximumBitmapSize() < UINT32(aSize.width) ||
mDC->GetMaximumBitmapSize() < UINT32(aSize.height)) {
// This is 'ok', so don't assert
gfxCriticalNote << "[D2D1.1] Attempt to use unsupported surface size " << aSize;
return false;
}
D2D1_BITMAP_PROPERTIES1 props;
props.dpiX = 96;
props.dpiY = 96;
props.pixelFormat = D2DPixelFormat(aFormat);
props.colorContext = nullptr;
props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;
hr = mDC->CreateBitmap(D2DIntSize(aSize), nullptr, 0, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1.1] 3CreateBitmap failure " << aSize << " Code: " << hexa(hr) << " format " << (int)aFormat;
return false;
}
mDC->SetTarget(CurrentTarget());
hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0), getter_AddRefs(mSolidColorBrush));
if (FAILED(hr)) {
gfxCriticalError() << "[D2D1.1] Failure creating solid color brush (I2).";
return false;
}
mDC->BeginDraw();
CurrentLayer().mIsOpaque = aFormat == SurfaceFormat::B8G8R8X8;
mDC->Clear();
mFormat = aFormat;
mSize = aSize;
return true;
}
/**
* Private helpers.
*/
uint32_t
DrawTargetD2D1::GetByteSize() const
{
return mSize.width * mSize.height * BytesPerPixel(mFormat);
}
RefPtr<ID2D1Factory1>
DrawTargetD2D1::factory()
{
StaticMutexAutoLock lock(Factory::mDeviceLock);
if (mFactory || !NS_IsMainThread()) {
return mFactory;
}
// We don't allow initializing the factory off the main thread.
MOZ_RELEASE_ASSERT(NS_IsMainThread());
RefPtr<ID2D1Factory> factory;
D2D1CreateFactoryFunc createD2DFactory;
HMODULE d2dModule = LoadLibraryW(L"d2d1.dll");
createD2DFactory = (D2D1CreateFactoryFunc)
GetProcAddress(d2dModule, "D2D1CreateFactory");
if (!createD2DFactory) {
gfxWarning() << "Failed to locate D2D1CreateFactory function.";
return nullptr;
}
D2D1_FACTORY_OPTIONS options;
#ifdef _DEBUG
options.debugLevel = D2D1_DEBUG_LEVEL_WARNING;
#else
options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
#endif
//options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
HRESULT hr = createD2DFactory(D2D1_FACTORY_TYPE_MULTI_THREADED,
__uuidof(ID2D1Factory),
&options,
getter_AddRefs(factory));
if (FAILED(hr) || !factory) {
gfxCriticalNote << "Failed to create a D2D1 content device: " << hexa(hr);
return nullptr;
}
RefPtr<ID2D1Factory1> factory1;
hr = factory->QueryInterface(__uuidof(ID2D1Factory1), getter_AddRefs(factory1));
if (FAILED(hr) || !factory1) {
return nullptr;
}
mFactory = factory1;
ExtendInputEffectD2D1::Register(mFactory);
RadialGradientEffectD2D1::Register(mFactory);
return mFactory;
}
void
DrawTargetD2D1::CleanupD2D()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
Factory::mDeviceLock.AssertCurrentThreadOwns();
if (mFactory) {
RadialGradientEffectD2D1::Unregister(mFactory);
ExtendInputEffectD2D1::Unregister(mFactory);
mFactory = nullptr;
}
}
void
DrawTargetD2D1::FlushInternal(bool aHasDependencyMutex /* = false */)
{
if (IsDeviceContextValid()) {
if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge ||
mTransformedGlyphsSinceLastPurge >= kTransformedGlyphsBeforePurge) &&
mPushedLayers.size() == 1) {
// It's important to pop all clips as otherwise layers can forget about
// their clip when doing an EndDraw. When we have layers pushed we cannot
// easily pop all underlying clips to delay the purge until we have no
// layers pushed.
PopAllClips();
mUsedCommandListsSincePurge = 0;
mTransformedGlyphsSinceLastPurge = 0;
mDC->EndDraw();
mDC->BeginDraw();
}
else {
mDC->Flush();
}
}
Maybe<StaticMutexAutoLock> lock;
if (!aHasDependencyMutex) {
lock.emplace(Factory::mDTDependencyLock);
}
Factory::mDTDependencyLock.AssertCurrentThreadOwns();
// We no longer depend on any target.
for (TargetSet::iterator iter = mDependingOnTargets.begin();
iter != mDependingOnTargets.end(); iter++) {
(*iter)->mDependentTargets.erase(this);
}
mDependingOnTargets.clear();
}
void
DrawTargetD2D1::MarkChanged()
{
if (mSnapshot) {
MutexAutoLock lock(*mSnapshotLock);
if (mSnapshot->hasOneRef()) {
// Just destroy it, since no-one else knows about it.
mSnapshot = nullptr;
} else {
mSnapshot->DrawTargetWillChange();
// The snapshot will no longer depend on this target.
MOZ_ASSERT(!mSnapshot);
}
}
{
StaticMutexAutoLock lock(Factory::mDTDependencyLock);
if (mDependentTargets.size()) {
// Copy mDependentTargets since the Flush()es below will modify it.
TargetSet tmpTargets = mDependentTargets;
for (TargetSet::iterator iter = tmpTargets.begin();
iter != tmpTargets.end(); iter++) {
(*iter)->FlushInternal(true);
}
// The Flush() should have broken all dependencies on this target.
MOZ_ASSERT(!mDependentTargets.size());
}
}
}
bool
DrawTargetD2D1::ShouldClipTemporarySurfaceDrawing(CompositionOp aOp,
const Pattern& aPattern,
bool aClipIsComplex)
{
bool patternSupported = IsPatternSupportedByD2D(aPattern);
return patternSupported && !CurrentLayer().mIsOpaque && D2DSupportsCompositeMode(aOp) &&
IsOperatorBoundByMask(aOp) && aClipIsComplex;
}
void
DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern)
{
MarkChanged();
bool patternSupported = IsPatternSupportedByD2D(aPattern);
if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) {
// It's important to do this before FlushTransformToDC! As this will cause
// the transform to become dirty.
PushAllClips();
FlushTransformToDC();
if (aOp != CompositionOp::OP_OVER)
mDC->SetPrimitiveBlend(D2DPrimitiveBlendMode(aOp));
return;
}
HRESULT result = mDC->CreateCommandList(getter_AddRefs(mCommandList));
mDC->SetTarget(mCommandList);
mUsedCommandListsSincePurge++;
// This is where we should have a valid command list. If we don't, something is
// wrong, and it's likely an OOM.
if (!mCommandList) {
gfxDevCrash(LogReason::InvalidCommandList) << "Invalid D2D1.1 command list on creation " << mUsedCommandListsSincePurge << ", " << gfx::hexa(result);
}
D2D1_RECT_F rect;
bool isAligned;
bool clipIsComplex = CurrentLayer().mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
PushClipsToDC(mDC);
}
FlushTransformToDC();
}
void
DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern)
{
bool patternSupported = IsPatternSupportedByD2D(aPattern);
if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) {
if (aOp != CompositionOp::OP_OVER)
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
return;
}
D2D1_RECT_F rect;
bool isAligned;
bool clipIsComplex = CurrentLayer().mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
PopClipsFromDC(mDC);
}
mDC->SetTarget(CurrentTarget());
if (!mCommandList) {
gfxDevCrash(LogReason::InvalidCommandList) << "Invalid D21.1 command list on finalize";
return;
}
mCommandList->Close();
RefPtr<ID2D1CommandList> source = mCommandList;
mCommandList = nullptr;
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
if (patternSupported) {
if (D2DSupportsCompositeMode(aOp)) {
RefPtr<ID2D1Image> tmpImage;
if (clipIsComplex) {
PopAllClips();
if (!IsOperatorBoundByMask(aOp)) {
tmpImage = GetImageForLayerContent();
}
}
mDC->DrawImage(source, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp));
if (tmpImage) {
RefPtr<ID2D1ImageBrush> brush;
RefPtr<ID2D1Geometry> inverseGeom = GetInverseClippedGeometry();
mDC->CreateImageBrush(tmpImage, D2D1::ImageBrushProperties(D2D1::RectF(0, 0, mSize.width, mSize.height)),
getter_AddRefs(brush));
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
mDC->FillGeometry(inverseGeom, brush);
mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
}
return;
}
RefPtr<ID2D1Effect> blendEffect;
HRESULT hr = mDC->CreateEffect(CLSID_D2D1Blend, getter_AddRefs(blendEffect));
if (FAILED(hr) || !blendEffect) {
gfxWarning() << "Failed to create blend effect!";
return;
}
// We don't need to preserve the current content of this layer as the output
// of the blend effect should completely replace it.
RefPtr<ID2D1Image> tmpImage = GetImageForLayerContent(false);
if (!tmpImage) {
return;
}
blendEffect->SetInput(0, tmpImage);
blendEffect->SetInput(1, source);
blendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp));
mDC->DrawImage(blendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
mComplexBlendsWithListInList++;
return;
}
const RadialGradientPattern *pat = static_cast<const RadialGradientPattern*>(&aPattern);
if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) {
// Draw nothing!
return;
}
if (!pat->mStops) {
// Draw nothing because of no color stops
return;
}
RefPtr<ID2D1Effect> radialGradientEffect;
HRESULT hr = mDC->CreateEffect(CLSID_RadialGradientEffect, getter_AddRefs(radialGradientEffect));
if (FAILED(hr) || !radialGradientEffect) {
gfxWarning() << "Failed to create radial gradient effect. Code: " << hexa(hr);
return;
}
radialGradientEffect->SetValue(RADIAL_PROP_STOP_COLLECTION,
static_cast<const GradientStopsD2D*>(pat->mStops.get())->mStopCollection);
radialGradientEffect->SetValue(RADIAL_PROP_CENTER_1, D2D1::Vector2F(pat->mCenter1.x, pat->mCenter1.y));
radialGradientEffect->SetValue(RADIAL_PROP_CENTER_2, D2D1::Vector2F(pat->mCenter2.x, pat->mCenter2.y));
radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_1, pat->mRadius1);
radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2);
radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2);
radialGradientEffect->SetValue(RADIAL_PROP_TRANSFORM, D2DMatrix(pat->mMatrix * mTransform));
radialGradientEffect->SetInput(0, source);
mDC->DrawImage(radialGradientEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp));
}
void
DrawTargetD2D1::AddDependencyOnSource(SourceSurfaceD2D1* aSource)
{
Maybe<MutexAutoLock> snapshotLock;
// We grab the SnapshotLock as well, this guaranteeds aSource->mDrawTarget
// cannot be cleared in between the if statement and the dereference.
if (aSource->mSnapshotLock) {
snapshotLock.emplace(*aSource->mSnapshotLock);
}
{
StaticMutexAutoLock lock(Factory::mDTDependencyLock);
if (aSource->mDrawTarget && !mDependingOnTargets.count(aSource->mDrawTarget)) {
aSource->mDrawTarget->mDependentTargets.insert(this);
mDependingOnTargets.insert(aSource->mDrawTarget);
}
}
}
static D2D1_RECT_F
IntersectRect(const D2D1_RECT_F& aRect1, const D2D1_RECT_F& aRect2)
{
D2D1_RECT_F result;
result.left = max(aRect1.left, aRect2.left);
result.top = max(aRect1.top, aRect2.top);
result.right = min(aRect1.right, aRect2.right);
result.bottom = min(aRect1.bottom, aRect2.bottom);
result.right = max(result.right, result.left);
result.bottom = max(result.bottom, result.top);
return result;
}
bool
DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned)
{
if (!CurrentLayer().mPushedClips.size()) {
return false;
}
aIsPixelAligned = true;
aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height);
for (auto iter = CurrentLayer().mPushedClips.begin();iter != CurrentLayer().mPushedClips.end(); iter++) {
if (iter->mGeometry) {
return false;
}
aClipRect = IntersectRect(aClipRect, iter->mBounds);
if (!iter->mIsPixelAligned) {
aIsPixelAligned = false;
}
}
return true;
}
static const uint32_t sComplexBlendsWithListAllowedInList = 4;
already_AddRefed<ID2D1Image>
DrawTargetD2D1::GetImageForLayerContent(bool aShouldPreserveContent)
{
PopAllClips();
if (!CurrentLayer().mCurrentList) {
RefPtr<ID2D1Bitmap> tmpBitmap;
HRESULT hr = mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), getter_AddRefs(tmpBitmap));
if (FAILED(hr)) {
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 6CreateBitmap failure " << mSize << " Code: " << hexa(hr) << " format " << (int)mFormat;
// If it's a recreate target error, return and handle it elsewhere.
if (hr == D2DERR_RECREATE_TARGET) {
mDC->Flush();
return nullptr;
}
// For now, crash in other scenarios; this should happen because tmpBitmap is
// null and CopyFromBitmap call below dereferences it.
}
mDC->Flush();
tmpBitmap->CopyFromBitmap(nullptr, mBitmap, nullptr);
return tmpBitmap.forget();
} else {
RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList;
mDC->CreateCommandList(getter_AddRefs(CurrentLayer().mCurrentList));
mDC->SetTarget(CurrentTarget());
list->Close();
RefPtr<ID2D1Bitmap1> tmpBitmap;
if (mComplexBlendsWithListInList >= sComplexBlendsWithListAllowedInList) {
D2D1_BITMAP_PROPERTIES1 props =
D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED));
mDC->CreateBitmap(mBitmap->GetPixelSize(), nullptr, 0, &props, getter_AddRefs(tmpBitmap));
mDC->SetTransform(D2D1::IdentityMatrix());
mDC->SetTarget(tmpBitmap);
mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
mDC->SetTarget(CurrentTarget());
mComplexBlendsWithListInList = 0;
}
DCCommandSink sink(mDC);
if (aShouldPreserveContent) {
list->Stream(&sink);
PushAllClips();
}
if (tmpBitmap) {
return tmpBitmap.forget();
}
return list.forget();
}
}
already_AddRefed<ID2D1Geometry>
DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
{
if (mCurrentClippedGeometry) {
*aClipBounds = mCurrentClipBounds;
RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry);
return clippedGeometry.forget();
}
MOZ_ASSERT(CurrentLayer().mPushedClips.size());
mCurrentClipBounds = IntRect(IntPoint(0, 0), mSize);
// if pathGeom is null then pathRect represents the path.
RefPtr<ID2D1Geometry> pathGeom;
D2D1_RECT_F pathRect;
bool pathRectIsAxisAligned = false;
auto iter = CurrentLayer().mPushedClips.begin();
if (iter->mGeometry) {
pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
} else {
pathRect = iter->mBounds;
pathRectIsAxisAligned = iter->mIsPixelAligned;
}
iter++;
for (;iter != CurrentLayer().mPushedClips.end(); iter++) {
// Do nothing but add it to the current clip bounds.
if (!iter->mGeometry && iter->mIsPixelAligned) {
mCurrentClipBounds.IntersectRect(mCurrentClipBounds,
IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top),
int32_t(iter->mBounds.right - iter->mBounds.left),
int32_t(iter->mBounds.bottom - iter->mBounds.top)));
continue;
}
if (!pathGeom) {
if (pathRectIsAxisAligned) {
mCurrentClipBounds.IntersectRect(mCurrentClipBounds,
IntRect(int32_t(pathRect.left), int32_t(pathRect.top),
int32_t(pathRect.right - pathRect.left),
int32_t(pathRect.bottom - pathRect.top)));
}
if (iter->mGeometry) {
// See if pathRect needs to go into the path geometry.
if (!pathRectIsAxisAligned) {
pathGeom = ConvertRectToGeometry(pathRect);
} else {
pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
}
} else {
pathRect = IntersectRect(pathRect, iter->mBounds);
pathRectIsAxisAligned = false;
continue;
}
}
RefPtr<ID2D1PathGeometry> newGeom;
factory()->CreatePathGeometry(getter_AddRefs(newGeom));
RefPtr<ID2D1GeometrySink> currentSink;
newGeom->Open(getter_AddRefs(currentSink));
if (iter->mGeometry) {
pathGeom->CombineWithGeometry(iter->mGeometry, D2D1_COMBINE_MODE_INTERSECT,
iter->mTransform, currentSink);
} else {
RefPtr<ID2D1Geometry> rectGeom = ConvertRectToGeometry(iter->mBounds);
pathGeom->CombineWithGeometry(rectGeom, D2D1_COMBINE_MODE_INTERSECT,
D2D1::IdentityMatrix(), currentSink);
}
currentSink->Close();
pathGeom = newGeom.forget();
}
// For now we need mCurrentClippedGeometry to always be non-nullptr. This
// method might seem a little strange but it is just fine, if pathGeom is
// nullptr pathRect will always still contain 1 clip unaccounted for
// regardless of mCurrentClipBounds.
if (!pathGeom) {
pathGeom = ConvertRectToGeometry(pathRect);
}
mCurrentClippedGeometry = pathGeom.forget();
*aClipBounds = mCurrentClipBounds;
RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry);
return clippedGeometry.forget();
}
already_AddRefed<ID2D1Geometry>
DrawTargetD2D1::GetInverseClippedGeometry()
{
IntRect bounds;
RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&bounds);
RefPtr<ID2D1RectangleGeometry> rectGeom;
RefPtr<ID2D1PathGeometry> inverseGeom;
factory()->CreateRectangleGeometry(D2D1::RectF(0, 0, mSize.width, mSize.height), getter_AddRefs(rectGeom));
factory()->CreatePathGeometry(getter_AddRefs(inverseGeom));
RefPtr<ID2D1GeometrySink> sink;
inverseGeom->Open(getter_AddRefs(sink));
rectGeom->CombineWithGeometry(geom, D2D1_COMBINE_MODE_EXCLUDE, D2D1::IdentityMatrix(), sink);
sink->Close();
return inverseGeom.forget();
}
void
DrawTargetD2D1::PopAllClips()
{
if (CurrentLayer().mClipsArePushed) {
PopClipsFromDC(mDC);
CurrentLayer().mClipsArePushed = false;
}
}
void
DrawTargetD2D1::PushAllClips()
{
if (!CurrentLayer().mClipsArePushed) {
PushClipsToDC(mDC);
CurrentLayer().mClipsArePushed = true;
}
}
void
DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext *aDC, bool aForceIgnoreAlpha, const D2D1_RECT_F& aMaxRect)
{
mDC->SetTransform(D2D1::IdentityMatrix());
mTransformDirty = true;
for (auto iter = CurrentLayer().mPushedClips.begin(); iter != CurrentLayer().mPushedClips.end(); iter++) {
if (iter->mGeometry) {
PushD2DLayer(aDC, iter->mGeometry, iter->mTransform, iter->mIsPixelAligned, aForceIgnoreAlpha, aMaxRect);
} else {
mDC->PushAxisAlignedClip(iter->mBounds, iter->mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
}
}
void
DrawTargetD2D1::PopClipsFromDC(ID2D1DeviceContext *aDC)
{
for (int i = CurrentLayer().mPushedClips.size() - 1; i >= 0; i--) {
if (CurrentLayer().mPushedClips[i].mGeometry) {
aDC->PopLayer();
} else {
aDC->PopAxisAlignedClip();
}
}
}
already_AddRefed<ID2D1Brush>
DrawTargetD2D1::CreateTransparentBlackBrush()
{
return GetSolidColorBrush(D2D1::ColorF(0, 0));
}
already_AddRefed<ID2D1SolidColorBrush>
DrawTargetD2D1::GetSolidColorBrush(const D2D_COLOR_F& aColor)
{
RefPtr<ID2D1SolidColorBrush> brush = mSolidColorBrush;
brush->SetColor(aColor);
return brush.forget();
}
already_AddRefed<ID2D1Brush>
DrawTargetD2D1::CreateBrushForPattern(const Pattern &aPattern, Float aAlpha)
{
if (!IsPatternSupportedByD2D(aPattern)) {
return GetSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f));
}
if (aPattern.GetType() == PatternType::COLOR) {
Color color = static_cast<const ColorPattern*>(&aPattern)->mColor;
return GetSolidColorBrush(D2D1::ColorF(color.r, color.g, color.b, color.a * aAlpha));
}
if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
RefPtr<ID2D1LinearGradientBrush> gradBrush;
const LinearGradientPattern *pat =
static_cast<const LinearGradientPattern*>(&aPattern);
GradientStopsD2D *stops = static_cast<GradientStopsD2D*>(pat->mStops.get());
if (!stops) {
gfxDebug() << "No stops specified for gradient pattern.";
return CreateTransparentBlackBrush();
}
if (pat->mBegin == pat->mEnd) {
uint32_t stopCount = stops->mStopCollection->GetGradientStopCount();
vector<D2D1_GRADIENT_STOP> d2dStops(stopCount);
stops->mStopCollection->GetGradientStops(&d2dStops.front(), stopCount);
d2dStops.back().color.a *= aAlpha;
return GetSolidColorBrush(d2dStops.back().color);
}
mDC->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin),
D2DPoint(pat->mEnd)),
D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)),
stops->mStopCollection,
getter_AddRefs(gradBrush));
if (!gradBrush) {
gfxWarning() << "Couldn't create gradient brush.";
return CreateTransparentBlackBrush();
}
return gradBrush.forget();
}
if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
RefPtr<ID2D1RadialGradientBrush> gradBrush;
const RadialGradientPattern *pat =
static_cast<const RadialGradientPattern*>(&aPattern);
GradientStopsD2D *stops = static_cast<GradientStopsD2D*>(pat->mStops.get());
if (!stops) {
gfxDebug() << "No stops specified for gradient pattern.";
return CreateTransparentBlackBrush();
}
// This will not be a complex radial gradient brush.
mDC->CreateRadialGradientBrush(
D2D1::RadialGradientBrushProperties(D2DPoint(pat->mCenter2),
D2DPoint(pat->mCenter1 - pat->mCenter2),
pat->mRadius2, pat->mRadius2),
D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)),
stops->mStopCollection,
getter_AddRefs(gradBrush));
if (!gradBrush) {
gfxWarning() << "Couldn't create gradient brush.";
return CreateTransparentBlackBrush();
}
return gradBrush.forget();
}
if (aPattern.GetType() == PatternType::SURFACE) {
const SurfacePattern *pat =
static_cast<const SurfacePattern*>(&aPattern);
if (!pat->mSurface) {
gfxDebug() << "No source surface specified for surface pattern";
return CreateTransparentBlackBrush();
}
D2D1_RECT_F samplingBounds;
Matrix mat = pat->mMatrix;
MOZ_ASSERT(pat->mSurface->IsValid());
RefPtr<ID2D1Image> image = GetImageForSurface(pat->mSurface, mat, pat->mExtendMode, !pat->mSamplingRect.IsEmpty() ? &pat->mSamplingRect : nullptr);
if (pat->mSurface->GetFormat() == SurfaceFormat::A8) {
// See bug 1251431, at least FillOpacityMask does not appear to allow a source bitmapbrush
// with source format A8. This creates a BGRA surface with the same alpha values that
// the A8 surface has.
RefPtr<ID2D1Bitmap> bitmap;
HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
if (SUCCEEDED(hr) && bitmap) {
RefPtr<ID2D1Image> oldTarget;
RefPtr<ID2D1Bitmap1> tmpBitmap;
mDC->CreateBitmap(D2D1::SizeU(pat->mSurface->GetSize().width, pat->mSurface->GetSize().height), nullptr, 0,
D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)),
getter_AddRefs(tmpBitmap));
mDC->GetTarget(getter_AddRefs(oldTarget));
mDC->SetTarget(tmpBitmap);
RefPtr<ID2D1SolidColorBrush> brush;
mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), getter_AddRefs(brush));
mDC->FillOpacityMask(bitmap, brush);
mDC->SetTarget(oldTarget);
image = tmpBitmap;
}
}
if (!image) {
return CreateTransparentBlackBrush();
}
if (pat->mSamplingRect.IsEmpty()) {
RefPtr<ID2D1Bitmap> bitmap;
HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
if (SUCCEEDED(hr) && bitmap) {
/**
* Create the brush with the proper repeat modes.
*/
RefPtr<ID2D1BitmapBrush> bitmapBrush;
D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
mDC->CreateBitmapBrush(bitmap,
D2D1::BitmapBrushProperties(xRepeat, yRepeat,
D2DFilter(pat->mSamplingFilter)),
D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
getter_AddRefs(bitmapBrush));
if (!bitmapBrush) {
gfxWarning() << "Couldn't create bitmap brush!";
return CreateTransparentBlackBrush();
}
return bitmapBrush.forget();
}
}
RefPtr<ID2D1ImageBrush> imageBrush;
if (pat->mSamplingRect.IsEmpty()) {
samplingBounds = D2D1::RectF(0, 0,
Float(pat->mSurface->GetSize().width),
Float(pat->mSurface->GetSize().height));
} else if (pat->mSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
samplingBounds = D2DRect(pat->mSamplingRect);
mat.PreTranslate(pat->mSamplingRect.X(), pat->mSamplingRect.Y());
} else {
// We will do a partial upload of the sampling restricted area from GetImageForSurface.
samplingBounds = D2D1::RectF(0, 0, pat->mSamplingRect.Width(), pat->mSamplingRect.Height());
}
D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS);
D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS);
mDC->CreateImageBrush(image,
D2D1::ImageBrushProperties(samplingBounds,
xRepeat,
yRepeat,
D2DInterpolationMode(pat->mSamplingFilter)),
D2D1::BrushProperties(aAlpha, D2DMatrix(mat)),
getter_AddRefs(imageBrush));
if (!imageBrush) {
gfxWarning() << "Couldn't create image brush!";
return CreateTransparentBlackBrush();
}
return imageBrush.forget();
}
gfxWarning() << "Invalid pattern type detected.";
return CreateTransparentBlackBrush();
}
already_AddRefed<ID2D1Image>
DrawTargetD2D1::GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform,
ExtendMode aExtendMode, const IntRect* aSourceRect,
bool aUserSpace)
{
RefPtr<ID2D1Image> image;
switch (aSurface->GetType()) {
case SurfaceType::CAPTURE:
{
SourceSurfaceCapture* capture = static_cast<SourceSurfaceCapture*>(aSurface);
RefPtr<SourceSurface> resolved = capture->Resolve(GetBackendType());
if (!resolved) {
return nullptr;
}
MOZ_ASSERT(resolved->GetType() != SurfaceType::CAPTURE);
return GetImageForSurface(resolved, aSourceTransform, aExtendMode, aSourceRect, aUserSpace);
}
break;
case SurfaceType::D2D1_1_IMAGE:
{
SourceSurfaceD2D1 *surf = static_cast<SourceSurfaceD2D1*>(aSurface);
image = surf->GetImage();
AddDependencyOnSource(surf);
}
break;
case SurfaceType::DUAL_DT:
{
// Sometimes we have a dual drawtarget but the underlying targets
// are d2d surfaces. Let's not readback and reupload in those cases.
SourceSurfaceDual* surface = static_cast<SourceSurfaceDual*>(aSurface);
SourceSurface* first = surface->GetFirstSurface();
if (first->GetType() == SurfaceType::D2D1_1_IMAGE) {
MOZ_ASSERT(surface->SameSurfaceTypes());
SourceSurfaceD2D1* d2dSurface = static_cast<SourceSurfaceD2D1*>(first);
image = d2dSurface->GetImage();
AddDependencyOnSource(d2dSurface);
break;
}
// Otherwise fall through
}
default:
{
RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
if (!dataSurf) {
gfxWarning() << "Invalid surface type.";
return nullptr;
}
Matrix transform = aUserSpace ? mTransform : Matrix();
return CreatePartialBitmapForSurface(dataSurf, transform, mSize, aExtendMode,
aSourceTransform, mDC, aSourceRect);
}
break;
}
return image.forget();
}
already_AddRefed<SourceSurface>
DrawTargetD2D1::OptimizeSourceSurface(SourceSurface* aSurface) const
{
if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
RefPtr<SourceSurface> surface(aSurface);
return surface.forget();
}
// Special case captures so we don't resolve them to a data surface.
if (aSurface->GetType() == SurfaceType::CAPTURE) {
SourceSurfaceCapture* capture = static_cast<SourceSurfaceCapture*>(aSurface);
RefPtr<SourceSurface> resolved = capture->Resolve(GetBackendType());
if (!resolved) {
return nullptr;
}
MOZ_ASSERT(resolved->GetType() != SurfaceType::CAPTURE);
return OptimizeSourceSurface(resolved);
}
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
RefPtr<ID2D1Bitmap1> bitmap;
{
DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
if (MOZ2D_WARN_IF(!map.IsMapped())) {
return nullptr;
}
HRESULT hr = mDC->CreateBitmap(D2DIntSize(data->GetSize()), map.GetData(), map.GetStride(),
D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, D2DPixelFormat(data->GetFormat())),
getter_AddRefs(bitmap));
if (FAILED(hr)) {
gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(data->GetSize()))) << "[D2D1.1] 4CreateBitmap failure " << data->GetSize() << " Code: " << hexa(hr) << " format " << (int)data->GetFormat();
}
}
if (!bitmap) {
return data.forget();
}
return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), mDC, data->GetFormat(), data->GetSize());
}
void
DrawTargetD2D1::PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform,
bool aPixelAligned, bool aForceIgnoreAlpha, const D2D1_RECT_F& aMaxRect)
{
D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE;
if (CurrentLayer().mIsOpaque || aForceIgnoreAlpha) {
options = D2D1_LAYER_OPTIONS1_IGNORE_ALPHA | D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
}
D2D1_ANTIALIAS_MODE antialias =
aPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry, antialias, aTransform,
1.0, nullptr, options), nullptr);
}
bool
DrawTargetD2D1::IsDeviceContextValid() {
uint32_t seqNo;
return Factory::GetD2D1Device(&seqNo) && seqNo == mDeviceSeq;
}
}
}