already_AddRefed<Path> nsSVGPathGeometryElement::GetOrBuildPath(const DrawTarget& aDrawTarget, FillRule aFillRule) { // We only cache the path if it matches the backend used for screen painting: bool cacheable = aDrawTarget.GetBackendType() == gfxPlatform::GetPlatform()->GetDefaultContentBackend(); // Checking for and returning mCachedPath before checking the pref means // that the pref is only live on page reload (or app restart for SVG in // chrome). The benefit is that we avoid causing a CPU memory cache miss by // looking at the global variable that the pref's stored in. if (cacheable && mCachedPath) { if (aDrawTarget.GetBackendType() == mCachedPath->GetBackendType()) { RefPtr<Path> path(mCachedPath); return path.forget(); } } RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(aFillRule); RefPtr<Path> path = BuildPath(builder); if (cacheable && NS_SVGPathCachingEnabled()) { mCachedPath = path; } return path.forget(); }
CGContextRef gfxQuartzNativeDrawing::BeginNativeDrawing() { NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress"); DrawTarget *dt = mDrawTarget; if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() || dt->GetBackendType() != BackendType::SKIA || dt->IsRecording()) { // We need a DrawTarget that we can get a CGContextRef from: Matrix transform = dt->GetTransform(); mNativeRect = transform.TransformBounds(mNativeRect); mNativeRect.RoundOut(); if (mNativeRect.IsEmpty()) { return nullptr; } mTempDrawTarget = Factory::CreateDrawTarget(BackendType::SKIA, IntSize::Truncate(mNativeRect.width, mNativeRect.height), SurfaceFormat::B8G8R8A8); if (!mTempDrawTarget) { return nullptr; } transform.PostTranslate(-mNativeRect.x, -mNativeRect.y); mTempDrawTarget->SetTransform(transform); dt = mTempDrawTarget; } else { // Clip the DT in case BorrowedCGContext needs to create a new layer. // This prevents it from creating a new layer the size of the window. // But make sure that this clip is device pixel aligned. Matrix transform = dt->GetTransform(); Rect deviceRect = transform.TransformBounds(mNativeRect); deviceRect.RoundOut(); mNativeRect = transform.Inverse().TransformBounds(deviceRect); mDrawTarget->PushClipRect(mNativeRect); } MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA); mCGContext = mBorrowedContext.Init(dt); if (NS_WARN_IF(!mCGContext)) { // Failed borrowing CG context, so we need to clean up. if (!mTempDrawTarget) { mDrawTarget->PopClip(); } return nullptr; } return mCGContext; }
static void RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface, const Rect& aDest, const Rect& aSrc, Rect& aSkipRect) { if (aSkipRect.Contains(aDest)) { return; } if ((!aDT.GetTransform().IsRectilinear() && aDT.GetBackendType() != BackendType::CAIRO) || (aDT.GetBackendType() == BackendType::DIRECT2D)) { // Use stretching if possible, since it leads to less seams when the // destination is transformed. However, don't do this if we're using cairo, // because if cairo is using pixman it won't render anything for large // stretch factors because pixman's internal fixed point precision is not // high enough to handle those scale factors. // Calling FillRect on a D2D backend with a repeating pattern is much slower // than DrawSurface, so special case the D2D backend here. aDT.DrawSurface(aSurface, aDest, aSrc); return; } SurfacePattern pattern(aSurface, ExtendMode::REPEAT, Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), Filter::GOOD, RoundedToInt(aSrc)); aDT.FillRect(aDest, pattern); }
CGContextRef gfxQuartzNativeDrawing::BeginNativeDrawing() { NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress"); if (mContext->IsCairo()) { // We're past that now. Any callers that still supply a Cairo context // don't deserve native theming. NS_WARNING("gfxQuartzNativeDrawing being used with a gfxContext that is not backed by a DrawTarget"); return nullptr; } DrawTarget *dt = mContext->GetDrawTarget(); if (dt->GetBackendType() != BackendType::COREGRAPHICS || dt->IsDualDrawTarget()) { IntSize backingSize(NSToIntFloor(mNativeRect.width * mBackingScale), NSToIntFloor(mNativeRect.height * mBackingScale)); if (backingSize.IsEmpty()) { return nullptr; } mDrawTarget = Factory::CreateDrawTarget(BackendType::COREGRAPHICS, backingSize, SurfaceFormat::B8G8R8A8); Matrix transform; transform.Scale(mBackingScale, mBackingScale); transform.Translate(-mNativeRect.x, -mNativeRect.y); mDrawTarget->SetTransform(transform); dt = mDrawTarget; } mCGContext = mBorrowedContext.Init(dt); MOZ_ASSERT(mCGContext); return mCGContext; }
static already_AddRefed<SourceSurface> GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, bool aMirrorCorners, IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aOutSlice, aRectSize); // We can get seams using the min size rect when drawing to the destination rect // if we have a non-pixel aligned destination transformation. In those cases, // fallback to just rendering the destination rect. Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix()); bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation(); if (useDestRect) { minSize = aRectSize; } aOutMinSize = minSize; DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); if (!useDestRect) { BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, destDT->GetBackendType()); if (cached) { // See CreateBoxShadow() for these values aOutBlurMargin = cached->mBlurMargin; RefPtr<SourceSurface> blur = cached->mBlur; return blur.forget(); } } RefPtr<SourceSurface> boxShadow = CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor, aMirrorCorners, aOutBlurMargin); if (!boxShadow) { return nullptr; } if (!useDestRect) { CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aOutBlurMargin, boxShadow); } return boxShadow.forget(); }
void CacheBlur(DrawTarget& aDT, const IntSize& aMinSize, const gfxIntSize& aBlurRadius, RectCornerRadii* aCornerRadii, const gfxRGBA& aShadowColor, IntMargin aExtendDest, SourceSurface* aBoxShadow) { BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType()); BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key); if (!gBlurCache->RegisterEntry(data)) { delete data; } }
HDC gfxWindowsNativeDrawing::BeginNativeDrawing() { if (mRenderState == RENDER_STATE_INIT) { RefPtr<gfxASurface> surf; DrawTarget* drawTarget = mContext->GetDrawTarget(); cairo_t* cairo = nullptr; if (drawTarget->GetBackendType() == BackendType::CAIRO) { cairo = static_cast<cairo_t*> (drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); if (cairo) { cairo_surface_t* s = cairo_get_target(cairo); if (s) { mDeviceOffset = mContext->GetDeviceOffset(); surf = gfxASurface::Wrap(s); } } } if (surf && surf->CairoStatus() != 0) return nullptr; gfxMatrix m = mContext->CurrentMatrix(); if (!m.HasNonTranslation()) mTransformType = TRANSLATION_ONLY; else if (m.HasNonAxisAlignedTransform()) mTransformType = COMPLEX; else mTransformType = AXIS_ALIGNED_SCALE; // if this is a native win32 surface, we don't have to // redirect rendering to our own HDC; in some cases, // we may be able to use the HDC from the surface directly. if (surf && ((surf->GetType() == gfxSurfaceType::Win32 || surf->GetType() == gfxSurfaceType::Win32Printing) && (surf->GetContentType() == gfxContentType::COLOR || (surf->GetContentType() == gfxContentType::COLOR_ALPHA && (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA))))) { // grab the DC. This can fail if there is a complex clipping path, // in which case we'll have to fall back. mWinSurface = static_cast<gfxWindowsSurface*>(static_cast<gfxASurface*>(surf.get())); mDC = cairo_win32_get_dc_with_clip(cairo); if (mDC) { if (mTransformType == TRANSLATION_ONLY) { mRenderState = RENDER_STATE_NATIVE_DRAWING; mTranslation = m.GetTranslation(); } else if (((mTransformType == AXIS_ALIGNED_SCALE) && (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) || (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM)) { mWorldTransform.eM11 = (FLOAT) m._11; mWorldTransform.eM12 = (FLOAT) m._12; mWorldTransform.eM21 = (FLOAT) m._21; mWorldTransform.eM22 = (FLOAT) m._22; mWorldTransform.eDx = (FLOAT) m._31; mWorldTransform.eDy = (FLOAT) m._32; mRenderState = RENDER_STATE_NATIVE_DRAWING; } } } // If we couldn't do native drawing, then we have to do two-buffer drawing // and do alpha recovery if (mRenderState == RENDER_STATE_INIT) { mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK; // We round out our native rect here, that way the snapping will // happen correctly. mNativeRect.RoundOut(); // we only do the scale bit if we can do an axis aligned // scale; otherwise we scale (if necessary) after // rendering with cairo. Note that if we're doing alpha recovery, // we cannot do a full complex transform with win32 (I mean, we could, but // it would require more code that's not here.) if (mTransformType == TRANSLATION_ONLY || !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { mScale = gfxSize(1.0, 1.0); // Add 1 to the surface size; it's guaranteed to not be incorrect, // and it fixes bug 382458 // There's probably a better fix, but I haven't figured out // the root cause of the problem. mTempSurfaceSize = IntSize((int32_t) ceil(mNativeRect.Width() + 1), (int32_t) ceil(mNativeRect.Height() + 1)); } else { // figure out the scale factors mScale = m.ScaleFactors(true); mWorldTransform.eM11 = (FLOAT) mScale.width; mWorldTransform.eM12 = 0.0f; mWorldTransform.eM21 = 0.0f; mWorldTransform.eM22 = (FLOAT) mScale.height; mWorldTransform.eDx = 0.0f; mWorldTransform.eDy = 0.0f; // See comment above about "+1" mTempSurfaceSize = IntSize((int32_t) ceil(mNativeRect.Width() * mScale.width + 1), (int32_t) ceil(mNativeRect.Height() * mScale.height + 1)); } } } if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { // we can just do native drawing directly to the context's surface // do we need to use SetWorldTransform? if (mTransformType != TRANSLATION_ONLY) { SetGraphicsMode(mDC, GM_ADVANCED); GetWorldTransform(mDC, &mOldWorldTransform); SetWorldTransform(mDC, &mWorldTransform); } GetViewportOrgEx(mDC, &mOrigViewportOrigin); SetViewportOrgEx(mDC, mOrigViewportOrigin.x - (int)mDeviceOffset.x, mOrigViewportOrigin.y - (int)mDeviceOffset.y, nullptr); return mDC; } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK || mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { // we're going to use mWinSurface to create our temporary surface here // get us a RGB24 DIB; DIB is important, because // we can later call GetImageSurface on it. mWinSurface = new gfxWindowsSurface(mTempSurfaceSize); mDC = mWinSurface->GetDC(); RECT r = { 0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height }; if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); else FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH)); if ((mTransformType != TRANSLATION_ONLY) && (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { SetGraphicsMode(mDC, GM_ADVANCED); SetWorldTransform(mDC, &mWorldTransform); } return mDC; } else { NS_ERROR("Bogus render state!"); return nullptr; } }
static already_AddRefed<SourceSurface> GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, bool aMirrorCorners, IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aOutSlice, aRectSize); // We can get seams using the min size rect when drawing to the destination rect // if we have a non-pixel aligned destination transformation. In those cases, // fallback to just rendering the destination rect. // During printing, we record all the Moz 2d commands and replay them on the parent side // with Cairo. Cairo printing uses StretchDIBits to stretch the surface. However, // since our source image is only 1px for some parts, we make thousands of calls. // Instead just render the blur ourself here as one image and send it over for printing. // TODO: May need to change this with the blob renderer in WR since it also records. Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix()); bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation() || aDestinationCtx->GetDrawTarget()->IsRecording(); if (useDestRect) { minSize = aRectSize; } aOutMinSize = minSize; DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); if (!useDestRect) { BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, destDT->GetBackendType()); if (cached) { // See CreateBoxShadow() for these values aOutBlurMargin = cached->mBlurMargin; RefPtr<SourceSurface> blur = cached->mBlur; return blur.forget(); } } RefPtr<SourceSurface> boxShadow = CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor, aMirrorCorners, aOutBlurMargin); if (!boxShadow) { return nullptr; } if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) { boxShadow = opt; } if (!useDestRect) { CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aOutBlurMargin, boxShadow); } return boxShadow.forget(); }
already_AddRefed<mozilla::gfx::SourceSurface> gfxAlphaBoxBlur::GetInsetBlur(Rect& aOuterRect, Rect& aInnerRect, const gfxIntSize& aBlurRadius, const gfxIntSize& aSpreadRadius, const RectCornerRadii& aInnerClipRadii, const Color& aShadowColor, const bool& aHasBorderRadius, IntPoint& aOutTopLeft, gfxContext* aDestinationCtx) { if (!gBlurCache) { gBlurCache = new BlurCache(); } gfxIntSize outerRectSize = RoundedToInt(aOuterRect).Size(); gfxIntSize innerRectSize = RoundedToInt(aInnerRect).Size(); DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(outerRectSize, innerRectSize, aBlurRadius, aSpreadRadius, &aInnerClipRadii, ThebesColor(aShadowColor), aHasBorderRadius, destDrawTarget->GetBackendType()); if (cached) { IntMargin extends = cached->mExtendDest; aOutTopLeft.x = extends.left; aOutTopLeft.y = extends.top; // So we don't forget the actual cached blur RefPtr<SourceSurface> cachedBlur = cached->mBlur; return cachedBlur.forget(); } // Dirty rect and skip rect are null for the min inset shadow. // When rendering inset box shadows, we respect the spread radius by changing // the shape of the unblurred shadow, and can pass a spread radius of zero here. gfxIntSize zeroSpread(0, 0); gfxContext* minGfxContext = Init(ThebesRect(aOuterRect), zeroSpread, aBlurRadius, nullptr, nullptr); if (!minGfxContext) { return nullptr; } DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget(); RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect, aInnerRect, aHasBorderRadius, aInnerClipRadii); minGfxContext->SetColor(ThebesColor(aShadowColor)); minGfxContext->SetPath(maskPath); minGfxContext->Fill(); RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &aOutTopLeft); if (!minMask) { return nullptr; } RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, ThebesColor(aShadowColor)); if (!minInsetBlur) { return nullptr; } IntMargin extendBy(aOutTopLeft.y, 0, 0, aOutTopLeft.x); CacheInsetBlur(outerRectSize, innerRectSize, aBlurRadius, aSpreadRadius, &aInnerClipRadii, ThebesColor(aShadowColor), aHasBorderRadius, destDrawTarget->GetBackendType(), extendBy, minInsetBlur); return minInsetBlur.forget(); }
/*** * If we can, let's paint this ClientPaintedLayer's contents off the main thread. * The essential idea is that we ask the ContentClient for a DrawTarget and record * the moz2d commands. On the Paint Thread, we replay those commands to the * destination draw target. There are a couple of lifetime issues here though: * * 1) TextureClient owns the underlying buffer and DrawTarget. Because of this * we have to keep the TextureClient and DrawTarget alive but trick the * TextureClient into thinking it's already returned the DrawTarget * since we iterate through different Rects to get DrawTargets*. If * the TextureClient goes away, the DrawTarget and thus buffer can too. * 2) When ContentClient::EndPaint happens, it flushes the DrawTarget. We have * to Reflush on the Paint Thread * 3) DrawTarget API is NOT thread safe. We get around this by recording * on the main thread and painting on the paint thread. Logically, * ClientLayerManager will force a flushed paint and block the main thread * if we have another transaction. Thus we have a gap between when the main * thread records, the paint thread paints, and we block the main thread * from trying to paint again. The underlying API however is NOT thread safe. * 4) We have both "sync" and "async" OMTP. Sync OMTP means we paint on the main thread * but block the main thread while the paint thread paints. Async OMTP doesn't block * the main thread. Sync OMTP is only meant to be used as a debugging tool. */ bool ClientPaintedLayer::PaintOffMainThread() { mContentClient->BeginAsyncPaint(); uint32_t flags = GetPaintFlags(); PaintState state = mContentClient->BeginPaintBuffer(this, flags); if (!UpdatePaintRegion(state)) { return false; } bool didUpdate = false; RotatedContentBuffer::DrawIterator iter; // Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP. while (RefPtr<CapturedPaintState> captureState = mContentClient->BorrowDrawTargetForRecording(state, &iter)) { DrawTarget* target = captureState->mTarget; if (!target || !target->IsValid()) { if (target) { mContentClient->ReturnDrawTargetToBuffer(target); } continue; } RefPtr<DrawTargetCapture> captureDT = Factory::CreateCaptureDrawTarget(target->GetBackendType(), target->GetSize(), target->GetFormat()); captureDT->SetTransform(captureState->mTargetTransform); SetAntialiasingFlags(this, captureDT); RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(captureDT); MOZ_ASSERT(ctx); // already checked the target above ClientManager()->GetPaintedLayerCallback()(this, ctx, iter.mDrawRegion, iter.mDrawRegion, state.mClip, state.mRegionToInvalidate, ClientManager()->GetPaintedLayerCallbackData()); ctx = nullptr; captureState->mCapture = captureDT.forget(); PaintThread::Get()->PaintContents(captureState, RotatedContentBuffer::PrepareDrawTargetForPainting); mContentClient->ReturnDrawTargetToBuffer(target); didUpdate = true; } mContentClient->EndPaint(nullptr); if (didUpdate) { UpdateContentClient(state); } return true; }