| |
| /* |
| * 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" |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| 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)) { |
| return false; |
| } |
| } else { |
| 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) { |
| SkAutoLockPixels alp(fBM); |
| 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()); |
| |
| 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)) { |
| return true; // path is empty so there is nothing to do |
| } |
| if (sw_draw_path_to_mask_texture(path, pathBounds, |
| fill, fContext, |
| translate, &ast, antiAlias)) { |
| GrTexture* texture = ast.texture(); |
| 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; |
| } |