/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrSoftwarePathRenderer.h" #include "GrPaint.h" #include "SkPaint.h" #include "GrRenderTarget.h" #include "GrContext.h" #include "SkDraw.h" #include "SkRasterClip.h" #include "GrGpu.h" //////////////////////////////////////////////////////////////////////////////// bool GrSoftwarePathRenderer::canDrawPath(const SkPath& path, GrPathFill fill, const GrDrawTarget* target, bool antiAlias) const { if (!antiAlias || NULL == fContext) { // TODO: We could allow the SW path to also handle non-AA paths but // this would mean that GrDefaultPathRenderer would never be called // (since it appears after the SW renderer in the path renderer // chain). Some testing would need to be done r.e. performance // and consistency of the resulting images before removing // the "!antiAlias" clause from the above test return false; } return true; } namespace { //////////////////////////////////////////////////////////////////////////////// SkPath::FillType gr_fill_to_sk_fill(GrPathFill fill) { switch (fill) { case kWinding_GrPathFill: return SkPath::kWinding_FillType; case kEvenOdd_GrPathFill: return SkPath::kEvenOdd_FillType; case kInverseWinding_GrPathFill: return SkPath::kInverseWinding_FillType; case kInverseEvenOdd_GrPathFill: return SkPath::kInverseEvenOdd_FillType; default: GrCrash("Unexpected fill."); return SkPath::kWinding_FillType; } } //////////////////////////////////////////////////////////////////////////////// // gets device coord bounds of path (not considering the fill) and clip. The // path bounds will be a subset of the clip bounds. returns false if // path bounds would be empty. bool get_path_and_clip_bounds(const GrDrawTarget* target, const SkPath& path, const GrVec* translate, GrIRect* pathBounds, GrIRect* clipBounds) { // compute bounds as intersection of rt size, clip, and path const GrRenderTarget* rt = target->getDrawState().getRenderTarget(); if (NULL == rt) { return false; } *pathBounds = GrIRect::MakeWH(rt->width(), rt->height()); const GrClip& clip = target->getClip(); if (clip.hasConservativeBounds()) { clip.getConservativeBounds().roundOut(clipBounds); if (!pathBounds->intersect(*clipBounds)) { return false; } } else { // pathBounds is currently the rt extent, set clip bounds to that rect. *clipBounds = *pathBounds; } GrRect pathSBounds = path.getBounds(); if (!pathSBounds.isEmpty()) { if (NULL != translate) { pathSBounds.offset(*translate); } target->getDrawState().getViewMatrix().mapRect(&pathSBounds, pathSBounds); GrIRect pathIBounds; pathSBounds.roundOut(&pathIBounds); if (!pathBounds->intersect(pathIBounds)) { // set the correct path bounds, as this would be used later. *pathBounds = pathIBounds; return false; } } else { *pathBounds = GrIRect::EmptyIRect(); return false; } return true; } /* * Convert a boolean operation into a transfer mode code */ SkXfermode::Mode op_to_mode(SkRegion::Op op) { static const SkXfermode::Mode modeMap[] = { SkXfermode::kDstOut_Mode, // kDifference_Op SkXfermode::kMultiply_Mode, // kIntersect_Op SkXfermode::kSrcOver_Mode, // kUnion_Op SkXfermode::kXor_Mode, // kXOR_Op SkXfermode::kClear_Mode, // kReverseDifference_Op SkXfermode::kSrc_Mode, // kReplace_Op }; return modeMap[op]; } } /** * Draw a single rect element of the clip stack into the accumulation bitmap */ void GrSWMaskHelper::draw(const GrRect& clientRect, SkRegion::Op op, bool antiAlias, GrColor color) { SkPaint paint; SkXfermode* mode = SkXfermode::Create(op_to_mode(op)); paint.setXfermode(mode); paint.setAntiAlias(antiAlias); paint.setColor(color); fDraw.drawRect(clientRect, paint); SkSafeUnref(mode); } /** * Draw a single path element of the clip stack into the accumulation bitmap */ void GrSWMaskHelper::draw(const SkPath& clientPath, SkRegion::Op op, GrPathFill fill, bool antiAlias, GrColor color) { SkPaint paint; SkPath tmpPath; const SkPath* pathToDraw = &clientPath; if (kHairLine_GrPathFill == fill) { paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(SK_Scalar1); } else { paint.setStyle(SkPaint::kFill_Style); SkPath::FillType skfill = gr_fill_to_sk_fill(fill); if (skfill != pathToDraw->getFillType()) { tmpPath = *pathToDraw; tmpPath.setFillType(skfill); pathToDraw = &tmpPath; } } SkXfermode* mode = SkXfermode::Create(op_to_mode(op)); paint.setXfermode(mode); paint.setAntiAlias(antiAlias); paint.setColor(color); fDraw.drawPath(*pathToDraw, paint); SkSafeUnref(mode); } bool GrSWMaskHelper::init(const GrIRect& pathDevBounds, const GrPoint* translate, bool useMatrix) { if (useMatrix) { fMatrix = fContext->getMatrix(); } else { fMatrix.setIdentity(); } if (NULL != translate) { fMatrix.postTranslate(translate->fX, translate->fY); } fMatrix.postTranslate(-pathDevBounds.fLeft * SK_Scalar1, -pathDevBounds.fTop * SK_Scalar1); GrIRect bounds = GrIRect::MakeWH(pathDevBounds.width(), pathDevBounds.height()); fBM.setConfig(SkBitmap::kA8_Config, bounds.fRight, bounds.fBottom); if (!fBM.allocPixels()) { return false; } sk_bzero(fBM.getPixels(), fBM.getSafeSize()); sk_bzero(&fDraw, sizeof(fDraw)); fRasterClip.setRect(bounds); fDraw.fRC = &fRasterClip; fDraw.fClip = &fRasterClip.bwRgn(); fDraw.fMatrix = &fMatrix; fDraw.fBitmap = &fBM; return true; } /** * Get a texture (from the texture cache) of the correct size & format */ bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* tex) { GrTextureDesc desc; desc.fWidth = fBM.width(); desc.fHeight = fBM.height(); desc.fConfig = kAlpha_8_GrPixelConfig; tex->set(fContext, desc); GrTexture* texture = tex->texture(); if (NULL == texture) { return false; } return true; } /** * Move the result of the software mask generation back to the gpu */ void GrSWMaskHelper::toTexture(GrTexture *texture, bool clearToWhite) { SkAutoLockPixels alp(fBM); // The destination texture is almost always larger than "fBM". Clear // it appropriately so we don't get mask artifacts outside of the path's // bounding box // "texture" needs to be installed as the render target for the clear // and the texture upload but cannot remain the render target upon // returned. Callers typically use it as a texture and it would then // be both source and dest. GrDrawState::AutoRenderTargetRestore artr(fContext->getGpu()->drawState(), texture->asRenderTarget()); if (clearToWhite) { fContext->getGpu()->clear(NULL, SK_ColorWHITE); } else { fContext->getGpu()->clear(NULL, 0x00000000); } texture->writePixels(0, 0, fBM.width(), fBM.height(), kAlpha_8_GrPixelConfig, fBM.getPixels(), fBM.rowBytes()); } namespace { //////////////////////////////////////////////////////////////////////////////// /** * sw rasterizes path to A8 mask using the context's matrix and uploads to a * scratch texture. */ bool sw_draw_path_to_mask_texture(const SkPath& clientPath, const GrIRect& pathDevBounds, GrPathFill fill, GrContext* context, const GrPoint* translate, GrAutoScratchTexture* tex, bool antiAlias) { GrSWMaskHelper helper(context); if (!helper.init(pathDevBounds, translate, true)) { return false; } helper.draw(clientPath, SkRegion::kReplace_Op, fill, antiAlias, SK_ColorWHITE); if (!helper.getTexture(tex)) { return false; } helper.toTexture(tex->texture(), false); return true; } //////////////////////////////////////////////////////////////////////////////// void draw_around_inv_path(GrDrawTarget* target, GrDrawState::StageMask stageMask, const GrIRect& clipBounds, const GrIRect& pathBounds) { GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask); GrRect rect; if (clipBounds.fTop < pathBounds.fTop) { rect.iset(clipBounds.fLeft, clipBounds.fTop, clipBounds.fRight, pathBounds.fTop); target->drawSimpleRect(rect, NULL, stageMask); } if (clipBounds.fLeft < pathBounds.fLeft) { rect.iset(clipBounds.fLeft, pathBounds.fTop, pathBounds.fLeft, pathBounds.fBottom); target->drawSimpleRect(rect, NULL, stageMask); } if (clipBounds.fRight > pathBounds.fRight) { rect.iset(pathBounds.fRight, pathBounds.fTop, clipBounds.fRight, pathBounds.fBottom); target->drawSimpleRect(rect, NULL, stageMask); } if (clipBounds.fBottom > pathBounds.fBottom) { rect.iset(clipBounds.fLeft, pathBounds.fBottom, clipBounds.fRight, clipBounds.fBottom); target->drawSimpleRect(rect, NULL, stageMask); } } } //////////////////////////////////////////////////////////////////////////////// // return true on success; false on failure bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path, GrPathFill fill, const GrVec* translate, GrDrawTarget* target, GrDrawState::StageMask stageMask, bool antiAlias) { if (NULL == fContext) { return false; } GrAutoScratchTexture ast; GrIRect pathBounds, clipBounds; if (!get_path_and_clip_bounds(target, path, translate, &pathBounds, &clipBounds)) { if (GrIsFillInverted(fill)) { draw_around_inv_path(target, stageMask, clipBounds, pathBounds); } return true; } if (sw_draw_path_to_mask_texture(path, pathBounds, fill, fContext, translate, &ast, antiAlias)) { SkAutoTUnref texture(ast.detach()); GrAssert(NULL != texture); GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask); enum { // the SW path renderer shares this stage with glyph // rendering (kGlyphMaskStage in GrBatchedTextContext) kPathMaskStage = GrPaint::kTotalStages, }; GrAssert(NULL == target->drawState()->getTexture(kPathMaskStage)); target->drawState()->setTexture(kPathMaskStage, texture); target->drawState()->sampler(kPathMaskStage)->reset(); GrScalar w = GrIntToScalar(pathBounds.width()); GrScalar h = GrIntToScalar(pathBounds.height()); GrRect maskRect = GrRect::MakeWH(w / texture->width(), h / texture->height()); const GrRect* srcRects[GrDrawState::kNumStages] = {NULL}; srcRects[kPathMaskStage] = &maskRect; stageMask |= 1 << kPathMaskStage; GrRect dstRect = GrRect::MakeLTRB( SK_Scalar1* pathBounds.fLeft, SK_Scalar1* pathBounds.fTop, SK_Scalar1* pathBounds.fRight, SK_Scalar1* pathBounds.fBottom); target->drawRect(dstRect, NULL, stageMask, srcRects, NULL); target->drawState()->setTexture(kPathMaskStage, NULL); if (GrIsFillInverted(fill)) { draw_around_inv_path(target, stageMask, clipBounds, pathBounds); } return true; } return false; }