michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkGpuDevice.h" michael@0: michael@0: #include "effects/GrBicubicEffect.h" michael@0: #include "effects/GrTextureDomain.h" michael@0: #include "effects/GrSimpleTextureEffect.h" michael@0: michael@0: #include "GrContext.h" michael@0: #include "GrBitmapTextContext.h" michael@0: #include "GrDistanceFieldTextContext.h" michael@0: michael@0: #include "SkGrTexturePixelRef.h" michael@0: michael@0: #include "SkBounder.h" michael@0: #include "SkColorFilter.h" michael@0: #include "SkDeviceImageFilterProxy.h" michael@0: #include "SkDrawProcs.h" michael@0: #include "SkGlyphCache.h" michael@0: #include "SkImageFilter.h" michael@0: #include "SkMaskFilter.h" michael@0: #include "SkPathEffect.h" michael@0: #include "SkPicture.h" michael@0: #include "SkRRect.h" michael@0: #include "SkStroke.h" michael@0: #include "SkSurface.h" michael@0: #include "SkTLazy.h" michael@0: #include "SkUtils.h" michael@0: #include "SkErrorInternals.h" michael@0: michael@0: #define CACHE_COMPATIBLE_DEVICE_TEXTURES 1 michael@0: michael@0: #if 0 michael@0: extern bool (*gShouldDrawProc)(); michael@0: #define CHECK_SHOULD_DRAW(draw, forceI) \ michael@0: do { \ michael@0: if (gShouldDrawProc && !gShouldDrawProc()) return; \ michael@0: this->prepareDraw(draw, forceI); \ michael@0: } while (0) michael@0: #else michael@0: #define CHECK_SHOULD_DRAW(draw, forceI) this->prepareDraw(draw, forceI) michael@0: #endif michael@0: michael@0: // This constant represents the screen alignment criterion in texels for michael@0: // requiring texture domain clamping to prevent color bleeding when drawing michael@0: // a sub region of a larger source image. michael@0: #define COLOR_BLEED_TOLERANCE 0.001f michael@0: michael@0: #define DO_DEFERRED_CLEAR() \ michael@0: do { \ michael@0: if (fNeedClear) { \ michael@0: this->clear(SK_ColorTRANSPARENT); \ michael@0: } \ michael@0: } while (false) \ michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #define CHECK_FOR_ANNOTATION(paint) \ michael@0: do { if (paint.getAnnotation()) { return; } } while (0) michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: class SkGpuDevice::SkAutoCachedTexture : public ::SkNoncopyable { michael@0: public: michael@0: SkAutoCachedTexture() michael@0: : fDevice(NULL) michael@0: , fTexture(NULL) { michael@0: } michael@0: michael@0: SkAutoCachedTexture(SkGpuDevice* device, michael@0: const SkBitmap& bitmap, michael@0: const GrTextureParams* params, michael@0: GrTexture** texture) michael@0: : fDevice(NULL) michael@0: , fTexture(NULL) { michael@0: SkASSERT(NULL != texture); michael@0: *texture = this->set(device, bitmap, params); michael@0: } michael@0: michael@0: ~SkAutoCachedTexture() { michael@0: if (NULL != fTexture) { michael@0: GrUnlockAndUnrefCachedBitmapTexture(fTexture); michael@0: } michael@0: } michael@0: michael@0: GrTexture* set(SkGpuDevice* device, michael@0: const SkBitmap& bitmap, michael@0: const GrTextureParams* params) { michael@0: if (NULL != fTexture) { michael@0: GrUnlockAndUnrefCachedBitmapTexture(fTexture); michael@0: fTexture = NULL; michael@0: } michael@0: fDevice = device; michael@0: GrTexture* result = (GrTexture*)bitmap.getTexture(); michael@0: if (NULL == result) { michael@0: // Cannot return the native texture so look it up in our cache michael@0: fTexture = GrLockAndRefCachedBitmapTexture(device->context(), bitmap, params); michael@0: result = fTexture; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: private: michael@0: SkGpuDevice* fDevice; michael@0: GrTexture* fTexture; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: struct GrSkDrawProcs : public SkDrawProcs { michael@0: public: michael@0: GrContext* fContext; michael@0: GrTextContext* fTextContext; michael@0: GrFontScaler* fFontScaler; // cached in the skia glyphcache michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static SkBitmap::Config grConfig2skConfig(GrPixelConfig config, bool* isOpaque) { michael@0: switch (config) { michael@0: case kAlpha_8_GrPixelConfig: michael@0: *isOpaque = false; michael@0: return SkBitmap::kA8_Config; michael@0: case kRGB_565_GrPixelConfig: michael@0: *isOpaque = true; michael@0: return SkBitmap::kRGB_565_Config; michael@0: case kRGBA_4444_GrPixelConfig: michael@0: *isOpaque = false; michael@0: return SkBitmap::kARGB_4444_Config; michael@0: case kSkia8888_GrPixelConfig: michael@0: // we don't currently have a way of knowing whether michael@0: // a 8888 is opaque based on the config. michael@0: *isOpaque = false; michael@0: return SkBitmap::kARGB_8888_Config; michael@0: default: michael@0: *isOpaque = false; michael@0: return SkBitmap::kNo_Config; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * GrRenderTarget does not know its opaqueness, only its config, so we have michael@0: * to make conservative guesses when we return an "equivalent" bitmap. michael@0: */ michael@0: static SkBitmap make_bitmap(GrContext* context, GrRenderTarget* renderTarget) { michael@0: bool isOpaque; michael@0: SkBitmap::Config config = grConfig2skConfig(renderTarget->config(), &isOpaque); michael@0: michael@0: SkBitmap bitmap; michael@0: bitmap.setConfig(config, renderTarget->width(), renderTarget->height(), 0, michael@0: isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType); michael@0: return bitmap; michael@0: } michael@0: michael@0: SkGpuDevice* SkGpuDevice::Create(GrSurface* surface) { michael@0: SkASSERT(NULL != surface); michael@0: if (NULL == surface->asRenderTarget() || NULL == surface->getContext()) { michael@0: return NULL; michael@0: } michael@0: if (surface->asTexture()) { michael@0: return SkNEW_ARGS(SkGpuDevice, (surface->getContext(), surface->asTexture())); michael@0: } else { michael@0: return SkNEW_ARGS(SkGpuDevice, (surface->getContext(), surface->asRenderTarget())); michael@0: } michael@0: } michael@0: michael@0: SkGpuDevice::SkGpuDevice(GrContext* context, GrTexture* texture) michael@0: : SkBitmapDevice(make_bitmap(context, texture->asRenderTarget())) { michael@0: this->initFromRenderTarget(context, texture->asRenderTarget(), false); michael@0: } michael@0: michael@0: SkGpuDevice::SkGpuDevice(GrContext* context, GrRenderTarget* renderTarget) michael@0: : SkBitmapDevice(make_bitmap(context, renderTarget)) { michael@0: this->initFromRenderTarget(context, renderTarget, false); michael@0: } michael@0: michael@0: void SkGpuDevice::initFromRenderTarget(GrContext* context, michael@0: GrRenderTarget* renderTarget, michael@0: bool cached) { michael@0: fDrawProcs = NULL; michael@0: michael@0: fContext = context; michael@0: fContext->ref(); michael@0: michael@0: fMainTextContext = SkNEW_ARGS(GrDistanceFieldTextContext, (fContext, fLeakyProperties)); michael@0: fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties)); michael@0: michael@0: fRenderTarget = NULL; michael@0: fNeedClear = false; michael@0: michael@0: SkASSERT(NULL != renderTarget); michael@0: fRenderTarget = renderTarget; michael@0: fRenderTarget->ref(); michael@0: michael@0: // Hold onto to the texture in the pixel ref (if there is one) because the texture holds a ref michael@0: // on the RT but not vice-versa. michael@0: // TODO: Remove this trickery once we figure out how to make SkGrPixelRef do this without michael@0: // busting chrome (for a currently unknown reason). michael@0: GrSurface* surface = fRenderTarget->asTexture(); michael@0: if (NULL == surface) { michael@0: surface = fRenderTarget; michael@0: } michael@0: michael@0: SkImageInfo info; michael@0: surface->asImageInfo(&info); michael@0: SkPixelRef* pr = SkNEW_ARGS(SkGrPixelRef, (info, surface, cached)); michael@0: michael@0: this->setPixelRef(pr)->unref(); michael@0: } michael@0: michael@0: SkGpuDevice* SkGpuDevice::Create(GrContext* context, const SkImageInfo& origInfo, michael@0: int sampleCount) { michael@0: if (kUnknown_SkColorType == origInfo.colorType() || michael@0: origInfo.width() < 0 || origInfo.height() < 0) { michael@0: return NULL; michael@0: } michael@0: michael@0: SkImageInfo info = origInfo; michael@0: // TODO: perhas we can loosen this check now that colortype is more detailed michael@0: // e.g. can we support both RGBA and BGRA here? michael@0: if (kRGB_565_SkColorType == info.colorType()) { michael@0: info.fAlphaType = kOpaque_SkAlphaType; // force this setting michael@0: } else { michael@0: info.fColorType = kPMColor_SkColorType; michael@0: if (kOpaque_SkAlphaType != info.alphaType()) { michael@0: info.fAlphaType = kPremul_SkAlphaType; // force this setting michael@0: } michael@0: } michael@0: michael@0: GrTextureDesc desc; michael@0: desc.fFlags = kRenderTarget_GrTextureFlagBit; michael@0: desc.fWidth = info.width(); michael@0: desc.fHeight = info.height(); michael@0: desc.fConfig = SkImageInfo2GrPixelConfig(info.colorType(), info.alphaType()); michael@0: desc.fSampleCnt = sampleCount; michael@0: michael@0: SkAutoTUnref texture(context->createUncachedTexture(desc, NULL, 0)); michael@0: if (!texture.get()) { michael@0: return NULL; michael@0: } michael@0: michael@0: return SkNEW_ARGS(SkGpuDevice, (context, texture.get())); michael@0: } michael@0: michael@0: #ifdef SK_SUPPORT_LEGACY_COMPATIBLEDEVICE_CONFIG michael@0: static SkBitmap make_bitmap(SkBitmap::Config config, int width, int height) { michael@0: SkBitmap bm; michael@0: bm.setConfig(SkImageInfo::Make(width, height, michael@0: SkBitmapConfigToColorType(config), michael@0: kPremul_SkAlphaType)); michael@0: return bm; michael@0: } michael@0: SkGpuDevice::SkGpuDevice(GrContext* context, michael@0: SkBitmap::Config config, michael@0: int width, michael@0: int height, michael@0: int sampleCount) michael@0: : SkBitmapDevice(make_bitmap(config, width, height)) michael@0: { michael@0: fDrawProcs = NULL; michael@0: michael@0: fContext = context; michael@0: fContext->ref(); michael@0: michael@0: fMainTextContext = SkNEW_ARGS(GrDistanceFieldTextContext, (fContext, fLeakyProperties)); michael@0: fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties)); michael@0: michael@0: fRenderTarget = NULL; michael@0: fNeedClear = false; michael@0: michael@0: if (config != SkBitmap::kRGB_565_Config) { michael@0: config = SkBitmap::kARGB_8888_Config; michael@0: } michael@0: michael@0: GrTextureDesc desc; michael@0: desc.fFlags = kRenderTarget_GrTextureFlagBit; michael@0: desc.fWidth = width; michael@0: desc.fHeight = height; michael@0: desc.fConfig = SkBitmapConfig2GrPixelConfig(config); michael@0: desc.fSampleCnt = sampleCount; michael@0: michael@0: SkImageInfo info; michael@0: if (!GrPixelConfig2ColorType(desc.fConfig, &info.fColorType)) { michael@0: sk_throw(); michael@0: } michael@0: info.fWidth = width; michael@0: info.fHeight = height; michael@0: info.fAlphaType = kPremul_SkAlphaType; michael@0: michael@0: SkAutoTUnref texture(fContext->createUncachedTexture(desc, NULL, 0)); michael@0: michael@0: if (NULL != texture) { michael@0: fRenderTarget = texture->asRenderTarget(); michael@0: fRenderTarget->ref(); michael@0: michael@0: SkASSERT(NULL != fRenderTarget); michael@0: michael@0: // wrap the bitmap with a pixelref to expose our texture michael@0: SkGrPixelRef* pr = SkNEW_ARGS(SkGrPixelRef, (info, texture)); michael@0: this->setPixelRef(pr)->unref(); michael@0: } else { michael@0: GrPrintf("--- failed to create gpu-offscreen [%d %d]\n", michael@0: width, height); michael@0: SkASSERT(false); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: SkGpuDevice::~SkGpuDevice() { michael@0: if (fDrawProcs) { michael@0: delete fDrawProcs; michael@0: } michael@0: michael@0: delete fMainTextContext; michael@0: delete fFallbackTextContext; michael@0: michael@0: // The GrContext takes a ref on the target. We don't want to cause the render michael@0: // target to be unnecessarily kept alive. michael@0: if (fContext->getRenderTarget() == fRenderTarget) { michael@0: fContext->setRenderTarget(NULL); michael@0: } michael@0: michael@0: if (fContext->getClip() == &fClipData) { michael@0: fContext->setClip(NULL); michael@0: } michael@0: michael@0: SkSafeUnref(fRenderTarget); michael@0: fContext->unref(); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkGpuDevice::makeRenderTargetCurrent() { michael@0: DO_DEFERRED_CLEAR(); michael@0: fContext->setRenderTarget(fRenderTarget); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: namespace { michael@0: GrPixelConfig config8888_to_grconfig_and_flags(SkCanvas::Config8888 config8888, uint32_t* flags) { michael@0: switch (config8888) { michael@0: case SkCanvas::kNative_Premul_Config8888: michael@0: *flags = 0; michael@0: return kSkia8888_GrPixelConfig; michael@0: case SkCanvas::kNative_Unpremul_Config8888: michael@0: *flags = GrContext::kUnpremul_PixelOpsFlag; michael@0: return kSkia8888_GrPixelConfig; michael@0: case SkCanvas::kBGRA_Premul_Config8888: michael@0: *flags = 0; michael@0: return kBGRA_8888_GrPixelConfig; michael@0: case SkCanvas::kBGRA_Unpremul_Config8888: michael@0: *flags = GrContext::kUnpremul_PixelOpsFlag; michael@0: return kBGRA_8888_GrPixelConfig; michael@0: case SkCanvas::kRGBA_Premul_Config8888: michael@0: *flags = 0; michael@0: return kRGBA_8888_GrPixelConfig; michael@0: case SkCanvas::kRGBA_Unpremul_Config8888: michael@0: *flags = GrContext::kUnpremul_PixelOpsFlag; michael@0: return kRGBA_8888_GrPixelConfig; michael@0: default: michael@0: GrCrash("Unexpected Config8888."); michael@0: *flags = 0; // suppress warning michael@0: return kSkia8888_GrPixelConfig; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool SkGpuDevice::onReadPixels(const SkBitmap& bitmap, michael@0: int x, int y, michael@0: SkCanvas::Config8888 config8888) { michael@0: DO_DEFERRED_CLEAR(); michael@0: SkASSERT(SkBitmap::kARGB_8888_Config == bitmap.config()); michael@0: SkASSERT(!bitmap.isNull()); michael@0: SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()))); michael@0: michael@0: SkAutoLockPixels alp(bitmap); michael@0: GrPixelConfig config; michael@0: uint32_t flags; michael@0: config = config8888_to_grconfig_and_flags(config8888, &flags); michael@0: return fContext->readRenderTargetPixels(fRenderTarget, michael@0: x, y, michael@0: bitmap.width(), michael@0: bitmap.height(), michael@0: config, michael@0: bitmap.getPixels(), michael@0: bitmap.rowBytes(), michael@0: flags); michael@0: } michael@0: michael@0: #ifdef SK_SUPPORT_LEGACY_WRITEPIXELSCONFIG michael@0: void SkGpuDevice::writePixels(const SkBitmap& bitmap, int x, int y, michael@0: SkCanvas::Config8888 config8888) { michael@0: SkAutoLockPixels alp(bitmap); michael@0: if (!bitmap.readyToDraw()) { michael@0: return; michael@0: } michael@0: michael@0: GrPixelConfig config; michael@0: uint32_t flags; michael@0: if (SkBitmap::kARGB_8888_Config == bitmap.config()) { michael@0: config = config8888_to_grconfig_and_flags(config8888, &flags); michael@0: } else { michael@0: flags = 0; michael@0: config= SkBitmapConfig2GrPixelConfig(bitmap.config()); michael@0: } michael@0: michael@0: fRenderTarget->writePixels(x, y, bitmap.width(), bitmap.height(), michael@0: config, bitmap.getPixels(), bitmap.rowBytes(), flags); michael@0: } michael@0: #endif michael@0: michael@0: bool SkGpuDevice::onWritePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, michael@0: int x, int y) { michael@0: // TODO: teach fRenderTarget to take ImageInfo directly to specify the src pixels michael@0: GrPixelConfig config = SkImageInfo2GrPixelConfig(info.colorType(), info.alphaType()); michael@0: if (kUnknown_GrPixelConfig == config) { michael@0: return false; michael@0: } michael@0: uint32_t flags = 0; michael@0: if (kUnpremul_SkAlphaType == info.alphaType()) { michael@0: flags = GrContext::kUnpremul_PixelOpsFlag; michael@0: } michael@0: fRenderTarget->writePixels(x, y, info.width(), info.height(), config, pixels, rowBytes, flags); michael@0: michael@0: // need to bump our genID for compatibility with clients that "know" we have a bitmap michael@0: this->onAccessBitmap().notifyPixelsChanged(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void SkGpuDevice::onAttachToCanvas(SkCanvas* canvas) { michael@0: INHERITED::onAttachToCanvas(canvas); michael@0: michael@0: // Canvas promises that this ptr is valid until onDetachFromCanvas is called michael@0: fClipData.fClipStack = canvas->getClipStack(); michael@0: } michael@0: michael@0: void SkGpuDevice::onDetachFromCanvas() { michael@0: INHERITED::onDetachFromCanvas(); michael@0: fClipData.fClipStack = NULL; michael@0: } michael@0: michael@0: // call this every draw call, to ensure that the context reflects our state, michael@0: // and not the state from some other canvas/device michael@0: void SkGpuDevice::prepareDraw(const SkDraw& draw, bool forceIdentity) { michael@0: SkASSERT(NULL != fClipData.fClipStack); michael@0: michael@0: fContext->setRenderTarget(fRenderTarget); michael@0: michael@0: SkASSERT(draw.fClipStack && draw.fClipStack == fClipData.fClipStack); michael@0: michael@0: if (forceIdentity) { michael@0: fContext->setIdentityMatrix(); michael@0: } else { michael@0: fContext->setMatrix(*draw.fMatrix); michael@0: } michael@0: fClipData.fOrigin = this->getOrigin(); michael@0: michael@0: fContext->setClip(&fClipData); michael@0: michael@0: DO_DEFERRED_CLEAR(); michael@0: } michael@0: michael@0: GrRenderTarget* SkGpuDevice::accessRenderTarget() { michael@0: DO_DEFERRED_CLEAR(); michael@0: return fRenderTarget; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: SK_COMPILE_ASSERT(SkShader::kNone_BitmapType == 0, shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kDefault_BitmapType == 1, shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kRadial_BitmapType == 2, shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kSweep_BitmapType == 3, shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kTwoPointRadial_BitmapType == 4, michael@0: shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kTwoPointConical_BitmapType == 5, michael@0: shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kLinear_BitmapType == 6, shader_type_mismatch); michael@0: SK_COMPILE_ASSERT(SkShader::kLast_BitmapType == 6, shader_type_mismatch); michael@0: michael@0: namespace { michael@0: michael@0: // converts a SkPaint to a GrPaint, ignoring the skPaint's shader michael@0: // justAlpha indicates that skPaint's alpha should be used rather than the color michael@0: // Callers may subsequently modify the GrPaint. Setting constantColor indicates michael@0: // that the final paint will draw the same color at every pixel. This allows michael@0: // an optimization where the the color filter can be applied to the skPaint's michael@0: // color once while converting to GrPaint and then ignored. michael@0: inline bool skPaint2GrPaintNoShader(SkGpuDevice* dev, michael@0: const SkPaint& skPaint, michael@0: bool justAlpha, michael@0: bool constantColor, michael@0: GrPaint* grPaint) { michael@0: michael@0: grPaint->setDither(skPaint.isDither()); michael@0: grPaint->setAntiAlias(skPaint.isAntiAlias()); michael@0: michael@0: SkXfermode::Coeff sm; michael@0: SkXfermode::Coeff dm; michael@0: michael@0: SkXfermode* mode = skPaint.getXfermode(); michael@0: GrEffectRef* xferEffect = NULL; michael@0: if (SkXfermode::AsNewEffectOrCoeff(mode, &xferEffect, &sm, &dm)) { michael@0: if (NULL != xferEffect) { michael@0: grPaint->addColorEffect(xferEffect)->unref(); michael@0: sm = SkXfermode::kOne_Coeff; michael@0: dm = SkXfermode::kZero_Coeff; michael@0: } michael@0: } else { michael@0: //SkDEBUGCODE(SkDebugf("Unsupported xfer mode.\n");) michael@0: #if 0 michael@0: return false; michael@0: #else michael@0: // Fall back to src-over michael@0: sm = SkXfermode::kOne_Coeff; michael@0: dm = SkXfermode::kISA_Coeff; michael@0: #endif michael@0: } michael@0: grPaint->setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm)); michael@0: michael@0: if (justAlpha) { michael@0: uint8_t alpha = skPaint.getAlpha(); michael@0: grPaint->setColor(GrColorPackRGBA(alpha, alpha, alpha, alpha)); michael@0: // justAlpha is currently set to true only if there is a texture, michael@0: // so constantColor should not also be true. michael@0: SkASSERT(!constantColor); michael@0: } else { michael@0: grPaint->setColor(SkColor2GrColor(skPaint.getColor())); michael@0: } michael@0: michael@0: SkColorFilter* colorFilter = skPaint.getColorFilter(); michael@0: if (NULL != colorFilter) { michael@0: // if the source color is a constant then apply the filter here once rather than per pixel michael@0: // in a shader. michael@0: if (constantColor) { michael@0: SkColor filtered = colorFilter->filterColor(skPaint.getColor()); michael@0: grPaint->setColor(SkColor2GrColor(filtered)); michael@0: } else { michael@0: SkAutoTUnref effect(colorFilter->asNewEffect(dev->context())); michael@0: if (NULL != effect.get()) { michael@0: grPaint->addColorEffect(effect); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // This function is similar to skPaint2GrPaintNoShader but also converts michael@0: // skPaint's shader to a GrTexture/GrEffectStage if possible. The texture to michael@0: // be used is set on grPaint and returned in param act. constantColor has the michael@0: // same meaning as in skPaint2GrPaintNoShader. michael@0: inline bool skPaint2GrPaintShader(SkGpuDevice* dev, michael@0: const SkPaint& skPaint, michael@0: bool constantColor, michael@0: GrPaint* grPaint) { michael@0: SkShader* shader = skPaint.getShader(); michael@0: if (NULL == shader) { michael@0: return skPaint2GrPaintNoShader(dev, skPaint, false, constantColor, grPaint); michael@0: } michael@0: michael@0: // SkShader::asNewEffect() may do offscreen rendering. Setup default drawing state and require michael@0: // the shader to set a render target . michael@0: GrContext::AutoWideOpenIdentityDraw awo(dev->context(), NULL); michael@0: michael@0: // setup the shader as the first color effect on the paint michael@0: SkAutoTUnref effect(shader->asNewEffect(dev->context(), skPaint)); michael@0: if (NULL != effect.get()) { michael@0: grPaint->addColorEffect(effect); michael@0: // Now setup the rest of the paint. michael@0: return skPaint2GrPaintNoShader(dev, skPaint, true, false, grPaint); michael@0: } else { michael@0: // We still don't have SkColorShader::asNewEffect() implemented. michael@0: SkShader::GradientInfo info; michael@0: SkColor color; michael@0: michael@0: info.fColors = &color; michael@0: info.fColorOffsets = NULL; michael@0: info.fColorCount = 1; michael@0: if (SkShader::kColor_GradientType == shader->asAGradient(&info)) { michael@0: SkPaint copy(skPaint); michael@0: copy.setShader(NULL); michael@0: // modulate the paint alpha by the shader's solid color alpha michael@0: U8CPU newA = SkMulDiv255Round(SkColorGetA(color), copy.getAlpha()); michael@0: copy.setColor(SkColorSetA(color, newA)); michael@0: return skPaint2GrPaintNoShader(dev, copy, false, constantColor, grPaint); michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: SkBitmap::Config SkGpuDevice::config() const { michael@0: if (NULL == fRenderTarget) { michael@0: return SkBitmap::kNo_Config; michael@0: } michael@0: michael@0: bool isOpaque; michael@0: return grConfig2skConfig(fRenderTarget->config(), &isOpaque); michael@0: } michael@0: michael@0: void SkGpuDevice::clear(SkColor color) { michael@0: SkIRect rect = SkIRect::MakeWH(this->width(), this->height()); michael@0: fContext->clear(&rect, SkColor2GrColor(color), true, fRenderTarget); michael@0: fNeedClear = false; michael@0: } michael@0: michael@0: void SkGpuDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: fContext->drawPaint(grPaint); michael@0: } michael@0: michael@0: // must be in SkCanvas::PointMode order michael@0: static const GrPrimitiveType gPointMode2PrimtiveType[] = { michael@0: kPoints_GrPrimitiveType, michael@0: kLines_GrPrimitiveType, michael@0: kLineStrip_GrPrimitiveType michael@0: }; michael@0: michael@0: void SkGpuDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, michael@0: size_t count, const SkPoint pts[], const SkPaint& paint) { michael@0: CHECK_FOR_ANNOTATION(paint); michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: SkScalar width = paint.getStrokeWidth(); michael@0: if (width < 0) { michael@0: return; michael@0: } michael@0: michael@0: // we only handle hairlines and paints without path effects or mask filters, michael@0: // else we let the SkDraw call our drawPath() michael@0: if (width > 0 || paint.getPathEffect() || paint.getMaskFilter()) { michael@0: draw.drawPoints(mode, count, pts, paint, true); michael@0: return; michael@0: } michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: fContext->drawVertices(grPaint, michael@0: gPointMode2PrimtiveType[mode], michael@0: SkToS32(count), michael@0: (GrPoint*)pts, michael@0: NULL, michael@0: NULL, michael@0: NULL, michael@0: 0); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkGpuDevice::drawRect(const SkDraw& draw, const SkRect& rect, michael@0: const SkPaint& paint) { michael@0: CHECK_FOR_ANNOTATION(paint); michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: bool doStroke = paint.getStyle() != SkPaint::kFill_Style; michael@0: SkScalar width = paint.getStrokeWidth(); michael@0: michael@0: /* michael@0: We have special code for hairline strokes, miter-strokes, bevel-stroke michael@0: and fills. Anything else we just call our path code. michael@0: */ michael@0: bool usePath = doStroke && width > 0 && michael@0: (paint.getStrokeJoin() == SkPaint::kRound_Join || michael@0: (paint.getStrokeJoin() == SkPaint::kBevel_Join && rect.isEmpty())); michael@0: // another two reasons we might need to call drawPath... michael@0: if (paint.getMaskFilter() || paint.getPathEffect()) { michael@0: usePath = true; michael@0: } michael@0: if (!usePath && paint.isAntiAlias() && !fContext->getMatrix().rectStaysRect()) { michael@0: #if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) michael@0: if (doStroke) { michael@0: #endif michael@0: usePath = true; michael@0: #if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) michael@0: } else { michael@0: usePath = !fContext->getMatrix().preservesRightAngles(); michael@0: } michael@0: #endif michael@0: } michael@0: // until we can both stroke and fill rectangles michael@0: if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) { michael@0: usePath = true; michael@0: } michael@0: michael@0: if (usePath) { michael@0: SkPath path; michael@0: path.addRect(rect); michael@0: this->drawPath(draw, path, paint, NULL, true); michael@0: return; michael@0: } michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: if (!doStroke) { michael@0: fContext->drawRect(grPaint, rect); michael@0: } else { michael@0: SkStrokeRec stroke(paint); michael@0: fContext->drawRect(grPaint, rect, &stroke); michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkGpuDevice::drawRRect(const SkDraw& draw, const SkRRect& rect, michael@0: const SkPaint& paint) { michael@0: CHECK_FOR_ANNOTATION(paint); michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkStrokeRec stroke(paint); michael@0: if (paint.getMaskFilter()) { michael@0: // try to hit the fast path for drawing filtered round rects michael@0: michael@0: SkRRect devRRect; michael@0: if (rect.transform(fContext->getMatrix(), &devRRect)) { michael@0: if (devRRect.allCornersCircular()) { michael@0: SkRect maskRect; michael@0: if (paint.getMaskFilter()->canFilterMaskGPU(devRRect.rect(), michael@0: draw.fClip->getBounds(), michael@0: fContext->getMatrix(), michael@0: &maskRect)) { michael@0: SkIRect finalIRect; michael@0: maskRect.roundOut(&finalIRect); michael@0: if (draw.fClip->quickReject(finalIRect)) { michael@0: // clipped out michael@0: return; michael@0: } michael@0: if (NULL != draw.fBounder && !draw.fBounder->doIRect(finalIRect)) { michael@0: // nothing to draw michael@0: return; michael@0: } michael@0: if (paint.getMaskFilter()->directFilterRRectMaskGPU(fContext, &grPaint, michael@0: stroke, devRRect)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: bool usePath = !rect.isSimple(); michael@0: // another two reasons we might need to call drawPath... michael@0: if (paint.getMaskFilter() || paint.getPathEffect()) { michael@0: usePath = true; michael@0: } michael@0: // until we can rotate rrects... michael@0: if (!usePath && !fContext->getMatrix().rectStaysRect()) { michael@0: usePath = true; michael@0: } michael@0: michael@0: if (usePath) { michael@0: SkPath path; michael@0: path.addRRect(rect); michael@0: this->drawPath(draw, path, paint, NULL, true); michael@0: return; michael@0: } michael@0: michael@0: fContext->drawRRect(grPaint, rect, stroke); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval, michael@0: const SkPaint& paint) { michael@0: CHECK_FOR_ANNOTATION(paint); michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: bool usePath = false; michael@0: // some basic reasons we might need to call drawPath... michael@0: if (paint.getMaskFilter() || paint.getPathEffect()) { michael@0: usePath = true; michael@0: } michael@0: michael@0: if (usePath) { michael@0: SkPath path; michael@0: path.addOval(oval); michael@0: this->drawPath(draw, path, paint, NULL, true); michael@0: return; michael@0: } michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: SkStrokeRec stroke(paint); michael@0: michael@0: fContext->drawOval(grPaint, oval, stroke); michael@0: } michael@0: michael@0: #include "SkMaskFilter.h" michael@0: #include "SkBounder.h" michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // helpers for applying mask filters michael@0: namespace { michael@0: michael@0: // Draw a mask using the supplied paint. Since the coverage/geometry michael@0: // is already burnt into the mask this boils down to a rect draw. michael@0: // Return true if the mask was successfully drawn. michael@0: bool draw_mask(GrContext* context, const SkRect& maskRect, michael@0: GrPaint* grp, GrTexture* mask) { michael@0: GrContext::AutoMatrix am; michael@0: if (!am.setIdentity(context, grp)) { michael@0: return false; michael@0: } michael@0: michael@0: SkMatrix matrix; michael@0: matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop); michael@0: matrix.postIDiv(mask->width(), mask->height()); michael@0: michael@0: grp->addCoverageEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref(); michael@0: context->drawRect(*grp, maskRect); michael@0: return true; michael@0: } michael@0: michael@0: bool draw_with_mask_filter(GrContext* context, const SkPath& devPath, michael@0: SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder, michael@0: GrPaint* grp, SkPaint::Style style) { michael@0: SkMask srcM, dstM; michael@0: michael@0: if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), filter, &context->getMatrix(), &srcM, michael@0: SkMask::kComputeBoundsAndRenderImage_CreateMode, style)) { michael@0: return false; michael@0: } michael@0: SkAutoMaskFreeImage autoSrc(srcM.fImage); michael@0: michael@0: if (!filter->filterMask(&dstM, srcM, context->getMatrix(), NULL)) { michael@0: return false; michael@0: } michael@0: // this will free-up dstM when we're done (allocated in filterMask()) michael@0: SkAutoMaskFreeImage autoDst(dstM.fImage); michael@0: michael@0: if (clip.quickReject(dstM.fBounds)) { michael@0: return false; michael@0: } michael@0: if (bounder && !bounder->doIRect(dstM.fBounds)) { michael@0: return false; michael@0: } michael@0: michael@0: // we now have a device-aligned 8bit mask in dstM, ready to be drawn using michael@0: // the current clip (and identity matrix) and GrPaint settings michael@0: GrTextureDesc desc; michael@0: desc.fWidth = dstM.fBounds.width(); michael@0: desc.fHeight = dstM.fBounds.height(); michael@0: desc.fConfig = kAlpha_8_GrPixelConfig; michael@0: michael@0: GrAutoScratchTexture ast(context, desc); michael@0: GrTexture* texture = ast.texture(); michael@0: michael@0: if (NULL == texture) { michael@0: return false; michael@0: } michael@0: texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, michael@0: dstM.fImage, dstM.fRowBytes); michael@0: michael@0: SkRect maskRect = SkRect::Make(dstM.fBounds); michael@0: michael@0: return draw_mask(context, maskRect, grp, texture); michael@0: } michael@0: michael@0: // Create a mask of 'devPath' and place the result in 'mask'. Return true on michael@0: // success; false otherwise. michael@0: bool create_mask_GPU(GrContext* context, michael@0: const SkRect& maskRect, michael@0: const SkPath& devPath, michael@0: const SkStrokeRec& stroke, michael@0: bool doAA, michael@0: GrAutoScratchTexture* mask) { michael@0: GrTextureDesc desc; michael@0: desc.fFlags = kRenderTarget_GrTextureFlagBit; michael@0: desc.fWidth = SkScalarCeilToInt(maskRect.width()); michael@0: desc.fHeight = SkScalarCeilToInt(maskRect.height()); michael@0: // We actually only need A8, but it often isn't supported as a michael@0: // render target so default to RGBA_8888 michael@0: desc.fConfig = kRGBA_8888_GrPixelConfig; michael@0: if (context->isConfigRenderable(kAlpha_8_GrPixelConfig, false)) { michael@0: desc.fConfig = kAlpha_8_GrPixelConfig; michael@0: } michael@0: michael@0: mask->set(context, desc); michael@0: if (NULL == mask->texture()) { michael@0: return false; michael@0: } michael@0: michael@0: GrTexture* maskTexture = mask->texture(); michael@0: SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height()); michael@0: michael@0: GrContext::AutoRenderTarget art(context, maskTexture->asRenderTarget()); michael@0: GrContext::AutoClip ac(context, clipRect); michael@0: michael@0: context->clear(NULL, 0x0, true); michael@0: michael@0: GrPaint tempPaint; michael@0: if (doAA) { michael@0: tempPaint.setAntiAlias(true); michael@0: // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst michael@0: // blend coeff of zero requires dual source blending support in order michael@0: // to properly blend partially covered pixels. This means the AA michael@0: // code path may not be taken. So we use a dst blend coeff of ISA. We michael@0: // could special case AA draws to a dst surface with known alpha=0 to michael@0: // use a zero dst coeff when dual source blending isn't available. michael@0: tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff); michael@0: } michael@0: michael@0: GrContext::AutoMatrix am; michael@0: michael@0: // Draw the mask into maskTexture with the path's top-left at the origin using tempPaint. michael@0: SkMatrix translate; michael@0: translate.setTranslate(-maskRect.fLeft, -maskRect.fTop); michael@0: am.set(context, translate); michael@0: context->drawPath(tempPaint, devPath, stroke); michael@0: return true; michael@0: } michael@0: michael@0: SkBitmap wrap_texture(GrTexture* texture) { michael@0: SkImageInfo info; michael@0: texture->asImageInfo(&info); michael@0: michael@0: SkBitmap result; michael@0: result.setConfig(info); michael@0: result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref(); michael@0: return result; michael@0: } michael@0: michael@0: }; michael@0: michael@0: void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath, michael@0: const SkPaint& paint, const SkMatrix* prePathMatrix, michael@0: bool pathIsMutable) { michael@0: CHECK_FOR_ANNOTATION(paint); michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: // If we have a prematrix, apply it to the path, optimizing for the case michael@0: // where the original path can in fact be modified in place (even though michael@0: // its parameter type is const). michael@0: SkPath* pathPtr = const_cast(&origSrcPath); michael@0: SkTLazy tmpPath; michael@0: SkTLazy effectPath; michael@0: michael@0: if (prePathMatrix) { michael@0: SkPath* result = pathPtr; michael@0: michael@0: if (!pathIsMutable) { michael@0: result = tmpPath.init(); michael@0: pathIsMutable = true; michael@0: } michael@0: // should I push prePathMatrix on our MV stack temporarily, instead michael@0: // of applying it here? See SkDraw.cpp michael@0: pathPtr->transform(*prePathMatrix, result); michael@0: pathPtr = result; michael@0: } michael@0: // at this point we're done with prePathMatrix michael@0: SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;) michael@0: michael@0: SkStrokeRec stroke(paint); michael@0: SkPathEffect* pathEffect = paint.getPathEffect(); michael@0: const SkRect* cullRect = NULL; // TODO: what is our bounds? michael@0: if (pathEffect && pathEffect->filterPath(effectPath.init(), *pathPtr, &stroke, michael@0: cullRect)) { michael@0: pathPtr = effectPath.get(); michael@0: pathIsMutable = true; michael@0: } michael@0: michael@0: if (paint.getMaskFilter()) { michael@0: if (!stroke.isHairlineStyle()) { michael@0: SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init(); michael@0: if (stroke.applyToPath(strokedPath, *pathPtr)) { michael@0: pathPtr = strokedPath; michael@0: pathIsMutable = true; michael@0: stroke.setFillStyle(); michael@0: } michael@0: } michael@0: michael@0: // avoid possibly allocating a new path in transform if we can michael@0: SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init(); michael@0: michael@0: // transform the path into device space michael@0: pathPtr->transform(fContext->getMatrix(), devPathPtr); michael@0: michael@0: SkRect maskRect; michael@0: if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(), michael@0: draw.fClip->getBounds(), michael@0: fContext->getMatrix(), michael@0: &maskRect)) { michael@0: // The context's matrix may change while creating the mask, so save the CTM here to michael@0: // pass to filterMaskGPU. michael@0: const SkMatrix ctm = fContext->getMatrix(); michael@0: michael@0: SkIRect finalIRect; michael@0: maskRect.roundOut(&finalIRect); michael@0: if (draw.fClip->quickReject(finalIRect)) { michael@0: // clipped out michael@0: return; michael@0: } michael@0: if (NULL != draw.fBounder && !draw.fBounder->doIRect(finalIRect)) { michael@0: // nothing to draw michael@0: return; michael@0: } michael@0: michael@0: if (paint.getMaskFilter()->directFilterMaskGPU(fContext, &grPaint, michael@0: stroke, *devPathPtr)) { michael@0: // the mask filter was able to draw itself directly, so there's nothing michael@0: // left to do. michael@0: return; michael@0: } michael@0: michael@0: GrAutoScratchTexture mask; michael@0: michael@0: if (create_mask_GPU(fContext, maskRect, *devPathPtr, stroke, michael@0: grPaint.isAntiAlias(), &mask)) { michael@0: GrTexture* filtered; michael@0: michael@0: if (paint.getMaskFilter()->filterMaskGPU(mask.texture(), michael@0: ctm, maskRect, &filtered, true)) { michael@0: // filterMaskGPU gives us ownership of a ref to the result michael@0: SkAutoTUnref atu(filtered); michael@0: michael@0: // If the scratch texture that we used as the filter src also holds the filter michael@0: // result then we must detach so that this texture isn't recycled for a later michael@0: // draw. michael@0: if (filtered == mask.texture()) { michael@0: mask.detach(); michael@0: filtered->unref(); // detach transfers GrAutoScratchTexture's ref to us. michael@0: } michael@0: michael@0: if (draw_mask(fContext, maskRect, &grPaint, filtered)) { michael@0: // This path is completely drawn michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // draw the mask on the CPU - this is a fallthrough path in case the michael@0: // GPU path fails michael@0: SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style : michael@0: SkPaint::kFill_Style; michael@0: draw_with_mask_filter(fContext, *devPathPtr, paint.getMaskFilter(), michael@0: *draw.fClip, draw.fBounder, &grPaint, style); michael@0: return; michael@0: } michael@0: michael@0: fContext->drawPath(grPaint, *pathPtr, stroke); michael@0: } michael@0: michael@0: static const int kBmpSmallTileSize = 1 << 10; michael@0: michael@0: static inline int get_tile_count(const SkIRect& srcRect, int tileSize) { michael@0: int tilesX = (srcRect.fRight / tileSize) - (srcRect.fLeft / tileSize) + 1; michael@0: int tilesY = (srcRect.fBottom / tileSize) - (srcRect.fTop / tileSize) + 1; michael@0: return tilesX * tilesY; michael@0: } michael@0: michael@0: static int determine_tile_size(const SkBitmap& bitmap, const SkIRect& src, int maxTileSize) { michael@0: if (maxTileSize <= kBmpSmallTileSize) { michael@0: return maxTileSize; michael@0: } michael@0: michael@0: size_t maxTileTotalTileSize = get_tile_count(src, maxTileSize); michael@0: size_t smallTotalTileSize = get_tile_count(src, kBmpSmallTileSize); michael@0: michael@0: maxTileTotalTileSize *= maxTileSize * maxTileSize; michael@0: smallTotalTileSize *= kBmpSmallTileSize * kBmpSmallTileSize; michael@0: michael@0: if (maxTileTotalTileSize > 2 * smallTotalTileSize) { michael@0: return kBmpSmallTileSize; michael@0: } else { michael@0: return maxTileSize; michael@0: } michael@0: } michael@0: michael@0: // Given a bitmap, an optional src rect, and a context with a clip and matrix determine what michael@0: // pixels from the bitmap are necessary. michael@0: static void determine_clipped_src_rect(const GrContext* context, michael@0: const SkBitmap& bitmap, michael@0: const SkRect* srcRectPtr, michael@0: SkIRect* clippedSrcIRect) { michael@0: const GrClipData* clip = context->getClip(); michael@0: clip->getConservativeBounds(context->getRenderTarget(), clippedSrcIRect, NULL); michael@0: SkMatrix inv; michael@0: if (!context->getMatrix().invert(&inv)) { michael@0: clippedSrcIRect->setEmpty(); michael@0: return; michael@0: } michael@0: SkRect clippedSrcRect = SkRect::Make(*clippedSrcIRect); michael@0: inv.mapRect(&clippedSrcRect); michael@0: if (NULL != srcRectPtr) { michael@0: // we've setup src space 0,0 to map to the top left of the src rect. michael@0: clippedSrcRect.offset(srcRectPtr->fLeft, srcRectPtr->fTop); michael@0: if (!clippedSrcRect.intersect(*srcRectPtr)) { michael@0: clippedSrcIRect->setEmpty(); michael@0: return; michael@0: } michael@0: } michael@0: clippedSrcRect.roundOut(clippedSrcIRect); michael@0: SkIRect bmpBounds = SkIRect::MakeWH(bitmap.width(), bitmap.height()); michael@0: if (!clippedSrcIRect->intersect(bmpBounds)) { michael@0: clippedSrcIRect->setEmpty(); michael@0: } michael@0: } michael@0: michael@0: bool SkGpuDevice::shouldTileBitmap(const SkBitmap& bitmap, michael@0: const GrTextureParams& params, michael@0: const SkRect* srcRectPtr, michael@0: int maxTileSize, michael@0: int* tileSize, michael@0: SkIRect* clippedSrcRect) const { michael@0: // if bitmap is explictly texture backed then just use the texture michael@0: if (NULL != bitmap.getTexture()) { michael@0: return false; michael@0: } michael@0: michael@0: // if it's larger than the max tile size, then we have no choice but tiling. michael@0: if (bitmap.width() > maxTileSize || bitmap.height() > maxTileSize) { michael@0: determine_clipped_src_rect(fContext, bitmap, srcRectPtr, clippedSrcRect); michael@0: *tileSize = determine_tile_size(bitmap, *clippedSrcRect, maxTileSize); michael@0: return true; michael@0: } michael@0: michael@0: if (bitmap.width() * bitmap.height() < 4 * kBmpSmallTileSize * kBmpSmallTileSize) { michael@0: return false; michael@0: } michael@0: michael@0: // if the entire texture is already in our cache then no reason to tile it michael@0: if (GrIsBitmapInCache(fContext, bitmap, ¶ms)) { michael@0: return false; michael@0: } michael@0: michael@0: // At this point we know we could do the draw by uploading the entire bitmap michael@0: // as a texture. However, if the texture would be large compared to the michael@0: // cache size and we don't require most of it for this draw then tile to michael@0: // reduce the amount of upload and cache spill. michael@0: michael@0: // assumption here is that sw bitmap size is a good proxy for its size as michael@0: // a texture michael@0: size_t bmpSize = bitmap.getSize(); michael@0: size_t cacheSize; michael@0: fContext->getTextureCacheLimits(NULL, &cacheSize); michael@0: if (bmpSize < cacheSize / 2) { michael@0: return false; michael@0: } michael@0: michael@0: // Figure out how much of the src we will need based on the src rect and clipping. michael@0: determine_clipped_src_rect(fContext, bitmap, srcRectPtr, clippedSrcRect); michael@0: *tileSize = kBmpSmallTileSize; // already know whole bitmap fits in one max sized tile. michael@0: size_t usedTileBytes = get_tile_count(*clippedSrcRect, kBmpSmallTileSize) * michael@0: kBmpSmallTileSize * kBmpSmallTileSize; michael@0: michael@0: return usedTileBytes < 2 * bmpSize; michael@0: } michael@0: michael@0: void SkGpuDevice::drawBitmap(const SkDraw& origDraw, michael@0: const SkBitmap& bitmap, michael@0: const SkMatrix& m, michael@0: const SkPaint& paint) { michael@0: SkMatrix concat; michael@0: SkTCopyOnFirstWrite draw(origDraw); michael@0: if (!m.isIdentity()) { michael@0: concat.setConcat(*draw->fMatrix, m); michael@0: draw.writable()->fMatrix = &concat; michael@0: } michael@0: this->drawBitmapCommon(*draw, bitmap, NULL, NULL, paint, SkCanvas::kNone_DrawBitmapRectFlag); michael@0: } michael@0: michael@0: // This method outsets 'iRect' by 'outset' all around and then clamps its extents to michael@0: // 'clamp'. 'offset' is adjusted to remain positioned over the top-left corner michael@0: // of 'iRect' for all possible outsets/clamps. michael@0: static inline void clamped_outset_with_offset(SkIRect* iRect, michael@0: int outset, michael@0: SkPoint* offset, michael@0: const SkIRect& clamp) { michael@0: iRect->outset(outset, outset); michael@0: michael@0: int leftClampDelta = clamp.fLeft - iRect->fLeft; michael@0: if (leftClampDelta > 0) { michael@0: offset->fX -= outset - leftClampDelta; michael@0: iRect->fLeft = clamp.fLeft; michael@0: } else { michael@0: offset->fX -= outset; michael@0: } michael@0: michael@0: int topClampDelta = clamp.fTop - iRect->fTop; michael@0: if (topClampDelta > 0) { michael@0: offset->fY -= outset - topClampDelta; michael@0: iRect->fTop = clamp.fTop; michael@0: } else { michael@0: offset->fY -= outset; michael@0: } michael@0: michael@0: if (iRect->fRight > clamp.fRight) { michael@0: iRect->fRight = clamp.fRight; michael@0: } michael@0: if (iRect->fBottom > clamp.fBottom) { michael@0: iRect->fBottom = clamp.fBottom; michael@0: } michael@0: } michael@0: michael@0: void SkGpuDevice::drawBitmapCommon(const SkDraw& draw, michael@0: const SkBitmap& bitmap, michael@0: const SkRect* srcRectPtr, michael@0: const SkSize* dstSizePtr, michael@0: const SkPaint& paint, michael@0: SkCanvas::DrawBitmapRectFlags flags) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: SkRect srcRect; michael@0: SkSize dstSize; michael@0: // If there is no src rect, or the src rect contains the entire bitmap then we're effectively michael@0: // in the (easier) bleed case, so update flags. michael@0: if (NULL == srcRectPtr) { michael@0: SkScalar w = SkIntToScalar(bitmap.width()); michael@0: SkScalar h = SkIntToScalar(bitmap.height()); michael@0: dstSize.fWidth = w; michael@0: dstSize.fHeight = h; michael@0: srcRect.set(0, 0, w, h); michael@0: flags = (SkCanvas::DrawBitmapRectFlags) (flags | SkCanvas::kBleed_DrawBitmapRectFlag); michael@0: } else { michael@0: SkASSERT(NULL != dstSizePtr); michael@0: srcRect = *srcRectPtr; michael@0: dstSize = *dstSizePtr; michael@0: if (srcRect.fLeft <= 0 && srcRect.fTop <= 0 && michael@0: srcRect.fRight >= bitmap.width() && srcRect.fBottom >= bitmap.height()) { michael@0: flags = (SkCanvas::DrawBitmapRectFlags) (flags | SkCanvas::kBleed_DrawBitmapRectFlag); michael@0: } michael@0: } michael@0: michael@0: if (paint.getMaskFilter()){ michael@0: // Convert the bitmap to a shader so that the rect can be drawn michael@0: // through drawRect, which supports mask filters. michael@0: SkBitmap tmp; // subset of bitmap, if necessary michael@0: const SkBitmap* bitmapPtr = &bitmap; michael@0: SkMatrix localM; michael@0: if (NULL != srcRectPtr) { michael@0: localM.setTranslate(-srcRectPtr->fLeft, -srcRectPtr->fTop); michael@0: localM.postScale(dstSize.fWidth / srcRectPtr->width(), michael@0: dstSize.fHeight / srcRectPtr->height()); michael@0: // In bleed mode we position and trim the bitmap based on the src rect which is michael@0: // already accounted for in 'm' and 'srcRect'. In clamp mode we need to chop out michael@0: // the desired portion of the bitmap and then update 'm' and 'srcRect' to michael@0: // compensate. michael@0: if (!(SkCanvas::kBleed_DrawBitmapRectFlag & flags)) { michael@0: SkIRect iSrc; michael@0: srcRect.roundOut(&iSrc); michael@0: michael@0: SkPoint offset = SkPoint::Make(SkIntToScalar(iSrc.fLeft), michael@0: SkIntToScalar(iSrc.fTop)); michael@0: michael@0: if (!bitmap.extractSubset(&tmp, iSrc)) { michael@0: return; // extraction failed michael@0: } michael@0: bitmapPtr = &tmp; michael@0: srcRect.offset(-offset.fX, -offset.fY); michael@0: michael@0: // The source rect has changed so update the matrix michael@0: localM.preTranslate(offset.fX, offset.fY); michael@0: } michael@0: } else { michael@0: localM.reset(); michael@0: } michael@0: michael@0: SkPaint paintWithShader(paint); michael@0: paintWithShader.setShader(SkShader::CreateBitmapShader(*bitmapPtr, michael@0: SkShader::kClamp_TileMode, SkShader::kClamp_TileMode))->unref(); michael@0: paintWithShader.getShader()->setLocalMatrix(localM); michael@0: SkRect dstRect = {0, 0, dstSize.fWidth, dstSize.fHeight}; michael@0: this->drawRect(draw, dstRect, paintWithShader); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // If there is no mask filter than it is OK to handle the src rect -> dst rect scaling using michael@0: // the view matrix rather than a local matrix. michael@0: SkMatrix m; michael@0: m.setScale(dstSize.fWidth / srcRect.width(), michael@0: dstSize.fHeight / srcRect.height()); michael@0: fContext->concatMatrix(m); michael@0: michael@0: GrTextureParams params; michael@0: SkPaint::FilterLevel paintFilterLevel = paint.getFilterLevel(); michael@0: GrTextureParams::FilterMode textureFilterMode; michael@0: michael@0: int tileFilterPad; michael@0: bool doBicubic = false; michael@0: michael@0: switch(paintFilterLevel) { michael@0: case SkPaint::kNone_FilterLevel: michael@0: tileFilterPad = 0; michael@0: textureFilterMode = GrTextureParams::kNone_FilterMode; michael@0: break; michael@0: case SkPaint::kLow_FilterLevel: michael@0: tileFilterPad = 1; michael@0: textureFilterMode = GrTextureParams::kBilerp_FilterMode; michael@0: break; michael@0: case SkPaint::kMedium_FilterLevel: michael@0: tileFilterPad = 1; michael@0: if (fContext->getMatrix().getMinStretch() < SK_Scalar1) { michael@0: textureFilterMode = GrTextureParams::kMipMap_FilterMode; michael@0: } else { michael@0: // Don't trigger MIP level generation unnecessarily. michael@0: textureFilterMode = GrTextureParams::kBilerp_FilterMode; michael@0: } michael@0: break; michael@0: case SkPaint::kHigh_FilterLevel: michael@0: // Minification can look bad with the bicubic effect. michael@0: if (fContext->getMatrix().getMinStretch() >= SK_Scalar1) { michael@0: // We will install an effect that does the filtering in the shader. michael@0: textureFilterMode = GrTextureParams::kNone_FilterMode; michael@0: tileFilterPad = GrBicubicEffect::kFilterTexelPad; michael@0: doBicubic = true; michael@0: } else { michael@0: textureFilterMode = GrTextureParams::kMipMap_FilterMode; michael@0: tileFilterPad = 1; michael@0: } michael@0: break; michael@0: default: michael@0: SkErrorInternals::SetError( kInvalidPaint_SkError, michael@0: "Sorry, I don't understand the filtering " michael@0: "mode you asked for. Falling back to " michael@0: "MIPMaps."); michael@0: tileFilterPad = 1; michael@0: textureFilterMode = GrTextureParams::kMipMap_FilterMode; michael@0: break; michael@0: } michael@0: michael@0: params.setFilterMode(textureFilterMode); michael@0: michael@0: int maxTileSize = fContext->getMaxTextureSize() - 2 * tileFilterPad; michael@0: int tileSize; michael@0: michael@0: SkIRect clippedSrcRect; michael@0: if (this->shouldTileBitmap(bitmap, params, srcRectPtr, maxTileSize, &tileSize, michael@0: &clippedSrcRect)) { michael@0: this->drawTiledBitmap(bitmap, srcRect, clippedSrcRect, params, paint, flags, tileSize, michael@0: doBicubic); michael@0: } else { michael@0: // take the simple case michael@0: this->internalDrawBitmap(bitmap, srcRect, params, paint, flags, doBicubic); michael@0: } michael@0: } michael@0: michael@0: // Break 'bitmap' into several tiles to draw it since it has already michael@0: // been determined to be too large to fit in VRAM michael@0: void SkGpuDevice::drawTiledBitmap(const SkBitmap& bitmap, michael@0: const SkRect& srcRect, michael@0: const SkIRect& clippedSrcIRect, michael@0: const GrTextureParams& params, michael@0: const SkPaint& paint, michael@0: SkCanvas::DrawBitmapRectFlags flags, michael@0: int tileSize, michael@0: bool bicubic) { michael@0: SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect); michael@0: michael@0: int nx = bitmap.width() / tileSize; michael@0: int ny = bitmap.height() / tileSize; michael@0: for (int x = 0; x <= nx; x++) { michael@0: for (int y = 0; y <= ny; y++) { michael@0: SkRect tileR; michael@0: tileR.set(SkIntToScalar(x * tileSize), michael@0: SkIntToScalar(y * tileSize), michael@0: SkIntToScalar((x + 1) * tileSize), michael@0: SkIntToScalar((y + 1) * tileSize)); michael@0: michael@0: if (!SkRect::Intersects(tileR, clippedSrcRect)) { michael@0: continue; michael@0: } michael@0: michael@0: if (!tileR.intersect(srcRect)) { michael@0: continue; michael@0: } michael@0: michael@0: SkBitmap tmpB; michael@0: SkIRect iTileR; michael@0: tileR.roundOut(&iTileR); michael@0: SkPoint offset = SkPoint::Make(SkIntToScalar(iTileR.fLeft), michael@0: SkIntToScalar(iTileR.fTop)); michael@0: michael@0: // Adjust the context matrix to draw at the right x,y in device space michael@0: SkMatrix tmpM; michael@0: GrContext::AutoMatrix am; michael@0: tmpM.setTranslate(offset.fX - srcRect.fLeft, offset.fY - srcRect.fTop); michael@0: am.setPreConcat(fContext, tmpM); michael@0: michael@0: if (SkPaint::kNone_FilterLevel != paint.getFilterLevel() || bicubic) { michael@0: SkIRect iClampRect; michael@0: michael@0: if (SkCanvas::kBleed_DrawBitmapRectFlag & flags) { michael@0: // In bleed mode we want to always expand the tile on all edges michael@0: // but stay within the bitmap bounds michael@0: iClampRect = SkIRect::MakeWH(bitmap.width(), bitmap.height()); michael@0: } else { michael@0: // In texture-domain/clamp mode we only want to expand the michael@0: // tile on edges interior to "srcRect" (i.e., we want to michael@0: // not bleed across the original clamped edges) michael@0: srcRect.roundOut(&iClampRect); michael@0: } michael@0: int outset = bicubic ? GrBicubicEffect::kFilterTexelPad : 1; michael@0: clamped_outset_with_offset(&iTileR, outset, &offset, iClampRect); michael@0: } michael@0: michael@0: if (bitmap.extractSubset(&tmpB, iTileR)) { michael@0: // now offset it to make it "local" to our tmp bitmap michael@0: tileR.offset(-offset.fX, -offset.fY); michael@0: michael@0: this->internalDrawBitmap(tmpB, tileR, params, paint, flags, bicubic); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static bool has_aligned_samples(const SkRect& srcRect, michael@0: const SkRect& transformedRect) { michael@0: // detect pixel disalignment michael@0: if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - michael@0: transformedRect.left()) < COLOR_BLEED_TOLERANCE && michael@0: SkScalarAbs(SkScalarRoundToScalar(transformedRect.top()) - michael@0: transformedRect.top()) < COLOR_BLEED_TOLERANCE && michael@0: SkScalarAbs(transformedRect.width() - srcRect.width()) < michael@0: COLOR_BLEED_TOLERANCE && michael@0: SkScalarAbs(transformedRect.height() - srcRect.height()) < michael@0: COLOR_BLEED_TOLERANCE) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool may_color_bleed(const SkRect& srcRect, michael@0: const SkRect& transformedRect, michael@0: const SkMatrix& m) { michael@0: // Only gets called if has_aligned_samples returned false. michael@0: // So we can assume that sampling is axis aligned but not texel aligned. michael@0: SkASSERT(!has_aligned_samples(srcRect, transformedRect)); michael@0: SkRect innerSrcRect(srcRect), innerTransformedRect, michael@0: outerTransformedRect(transformedRect); michael@0: innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf); michael@0: m.mapRect(&innerTransformedRect, innerSrcRect); michael@0: michael@0: // The gap between outerTransformedRect and innerTransformedRect michael@0: // represents the projection of the source border area, which is michael@0: // problematic for color bleeding. We must check whether any michael@0: // destination pixels sample the border area. michael@0: outerTransformedRect.inset(COLOR_BLEED_TOLERANCE, COLOR_BLEED_TOLERANCE); michael@0: innerTransformedRect.outset(COLOR_BLEED_TOLERANCE, COLOR_BLEED_TOLERANCE); michael@0: SkIRect outer, inner; michael@0: outerTransformedRect.round(&outer); michael@0: innerTransformedRect.round(&inner); michael@0: // If the inner and outer rects round to the same result, it means the michael@0: // border does not overlap any pixel centers. Yay! michael@0: return inner != outer; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * This is called by drawBitmap(), which has to handle images that may be too michael@0: * large to be represented by a single texture. michael@0: * michael@0: * internalDrawBitmap assumes that the specified bitmap will fit in a texture michael@0: * and that non-texture portion of the GrPaint has already been setup. michael@0: */ michael@0: void SkGpuDevice::internalDrawBitmap(const SkBitmap& bitmap, michael@0: const SkRect& srcRect, michael@0: const GrTextureParams& params, michael@0: const SkPaint& paint, michael@0: SkCanvas::DrawBitmapRectFlags flags, michael@0: bool bicubic) { michael@0: SkASSERT(bitmap.width() <= fContext->getMaxTextureSize() && michael@0: bitmap.height() <= fContext->getMaxTextureSize()); michael@0: michael@0: GrTexture* texture; michael@0: SkAutoCachedTexture act(this, bitmap, ¶ms, &texture); michael@0: if (NULL == texture) { michael@0: return; michael@0: } michael@0: michael@0: SkRect dstRect = {0, 0, srcRect.width(), srcRect.height() }; michael@0: SkRect paintRect; michael@0: SkScalar wInv = SkScalarInvert(SkIntToScalar(texture->width())); michael@0: SkScalar hInv = SkScalarInvert(SkIntToScalar(texture->height())); michael@0: paintRect.setLTRB(SkScalarMul(srcRect.fLeft, wInv), michael@0: SkScalarMul(srcRect.fTop, hInv), michael@0: SkScalarMul(srcRect.fRight, wInv), michael@0: SkScalarMul(srcRect.fBottom, hInv)); michael@0: michael@0: bool needsTextureDomain = false; michael@0: if (!(flags & SkCanvas::kBleed_DrawBitmapRectFlag) && michael@0: (bicubic || params.filterMode() != GrTextureParams::kNone_FilterMode)) { michael@0: // Need texture domain if drawing a sub rect michael@0: needsTextureDomain = srcRect.width() < bitmap.width() || michael@0: srcRect.height() < bitmap.height(); michael@0: if (!bicubic && needsTextureDomain && fContext->getMatrix().rectStaysRect()) { michael@0: const SkMatrix& matrix = fContext->getMatrix(); michael@0: // sampling is axis-aligned michael@0: SkRect transformedRect; michael@0: matrix.mapRect(&transformedRect, srcRect); michael@0: michael@0: if (has_aligned_samples(srcRect, transformedRect)) { michael@0: // We could also turn off filtering here (but we already did a cache lookup with michael@0: // params). michael@0: needsTextureDomain = false; michael@0: } else { michael@0: needsTextureDomain = may_color_bleed(srcRect, transformedRect, matrix); michael@0: } michael@0: } michael@0: } michael@0: michael@0: SkRect textureDomain = SkRect::MakeEmpty(); michael@0: SkAutoTUnref effect; michael@0: if (needsTextureDomain) { michael@0: // Use a constrained texture domain to avoid color bleeding michael@0: SkScalar left, top, right, bottom; michael@0: if (srcRect.width() > SK_Scalar1) { michael@0: SkScalar border = SK_ScalarHalf / texture->width(); michael@0: left = paintRect.left() + border; michael@0: right = paintRect.right() - border; michael@0: } else { michael@0: left = right = SkScalarHalf(paintRect.left() + paintRect.right()); michael@0: } michael@0: if (srcRect.height() > SK_Scalar1) { michael@0: SkScalar border = SK_ScalarHalf / texture->height(); michael@0: top = paintRect.top() + border; michael@0: bottom = paintRect.bottom() - border; michael@0: } else { michael@0: top = bottom = SkScalarHalf(paintRect.top() + paintRect.bottom()); michael@0: } michael@0: textureDomain.setLTRB(left, top, right, bottom); michael@0: if (bicubic) { michael@0: effect.reset(GrBicubicEffect::Create(texture, SkMatrix::I(), textureDomain)); michael@0: } else { michael@0: effect.reset(GrTextureDomainEffect::Create(texture, michael@0: SkMatrix::I(), michael@0: textureDomain, michael@0: GrTextureDomain::kClamp_Mode, michael@0: params.filterMode())); michael@0: } michael@0: } else if (bicubic) { michael@0: SkASSERT(GrTextureParams::kNone_FilterMode == params.filterMode()); michael@0: SkShader::TileMode tileModes[2] = { params.getTileModeX(), params.getTileModeY() }; michael@0: effect.reset(GrBicubicEffect::Create(texture, SkMatrix::I(), tileModes)); michael@0: } else { michael@0: effect.reset(GrSimpleTextureEffect::Create(texture, SkMatrix::I(), params)); michael@0: } michael@0: michael@0: // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring michael@0: // the rest from the SkPaint. michael@0: GrPaint grPaint; michael@0: grPaint.addColorEffect(effect); michael@0: bool alphaOnly = !(SkBitmap::kA8_Config == bitmap.config()); michael@0: if (!skPaint2GrPaintNoShader(this, paint, alphaOnly, false, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: fContext->drawRectToRect(grPaint, dstRect, paintRect, NULL); michael@0: } michael@0: michael@0: static bool filter_texture(SkBaseDevice* device, GrContext* context, michael@0: GrTexture* texture, const SkImageFilter* filter, michael@0: int w, int h, const SkImageFilter::Context& ctx, michael@0: SkBitmap* result, SkIPoint* offset) { michael@0: SkASSERT(filter); michael@0: SkDeviceImageFilterProxy proxy(device); michael@0: michael@0: if (filter->canFilterImageGPU()) { michael@0: // Save the render target and set it to NULL, so we don't accidentally draw to it in the michael@0: // filter. Also set the clip wide open and the matrix to identity. michael@0: GrContext::AutoWideOpenIdentityDraw awo(context, NULL); michael@0: return filter->filterImageGPU(&proxy, wrap_texture(texture), ctx, result, offset); michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, michael@0: int left, int top, const SkPaint& paint) { michael@0: // drawSprite is defined to be in device coords. michael@0: CHECK_SHOULD_DRAW(draw, true); michael@0: michael@0: SkAutoLockPixels alp(bitmap, !bitmap.getTexture()); michael@0: if (!bitmap.getTexture() && !bitmap.readyToDraw()) { michael@0: return; michael@0: } michael@0: michael@0: int w = bitmap.width(); michael@0: int h = bitmap.height(); michael@0: michael@0: GrTexture* texture; michael@0: // draw sprite uses the default texture params michael@0: SkAutoCachedTexture act(this, bitmap, NULL, &texture); michael@0: michael@0: SkImageFilter* filter = paint.getImageFilter(); michael@0: // This bitmap will own the filtered result as a texture. michael@0: SkBitmap filteredBitmap; michael@0: michael@0: if (NULL != filter) { michael@0: SkIPoint offset = SkIPoint::Make(0, 0); michael@0: SkMatrix matrix(*draw.fMatrix); michael@0: matrix.postTranslate(SkIntToScalar(-left), SkIntToScalar(-top)); michael@0: SkIRect clipBounds = SkIRect::MakeWH(bitmap.width(), bitmap.height()); michael@0: SkImageFilter::Context ctx(matrix, clipBounds); michael@0: if (filter_texture(this, fContext, texture, filter, w, h, ctx, &filteredBitmap, michael@0: &offset)) { michael@0: texture = (GrTexture*) filteredBitmap.getTexture(); michael@0: w = filteredBitmap.width(); michael@0: h = filteredBitmap.height(); michael@0: left += offset.x(); michael@0: top += offset.y(); michael@0: } else { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: GrPaint grPaint; michael@0: grPaint.addColorTextureEffect(texture, SkMatrix::I()); michael@0: michael@0: if(!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: fContext->drawRectToRect(grPaint, michael@0: SkRect::MakeXYWH(SkIntToScalar(left), michael@0: SkIntToScalar(top), michael@0: SkIntToScalar(w), michael@0: SkIntToScalar(h)), michael@0: SkRect::MakeXYWH(0, michael@0: 0, michael@0: SK_Scalar1 * w / texture->width(), michael@0: SK_Scalar1 * h / texture->height())); michael@0: } michael@0: michael@0: void SkGpuDevice::drawBitmapRect(const SkDraw& origDraw, const SkBitmap& bitmap, michael@0: const SkRect* src, const SkRect& dst, michael@0: const SkPaint& paint, michael@0: SkCanvas::DrawBitmapRectFlags flags) { michael@0: SkMatrix matrix; michael@0: SkRect bitmapBounds, tmpSrc; michael@0: michael@0: bitmapBounds.set(0, 0, michael@0: SkIntToScalar(bitmap.width()), michael@0: SkIntToScalar(bitmap.height())); michael@0: michael@0: // Compute matrix from the two rectangles michael@0: if (NULL != src) { michael@0: tmpSrc = *src; michael@0: } else { michael@0: tmpSrc = bitmapBounds; michael@0: } michael@0: michael@0: matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); michael@0: michael@0: // clip the tmpSrc to the bounds of the bitmap. No check needed if src==null. michael@0: if (NULL != src) { michael@0: if (!bitmapBounds.contains(tmpSrc)) { michael@0: if (!tmpSrc.intersect(bitmapBounds)) { michael@0: return; // nothing to draw michael@0: } michael@0: } michael@0: } michael@0: michael@0: SkRect tmpDst; michael@0: matrix.mapRect(&tmpDst, tmpSrc); michael@0: michael@0: SkTCopyOnFirstWrite draw(origDraw); michael@0: if (0 != tmpDst.fLeft || 0 != tmpDst.fTop) { michael@0: // Translate so that tempDst's top left is at the origin. michael@0: matrix = *origDraw.fMatrix; michael@0: matrix.preTranslate(tmpDst.fLeft, tmpDst.fTop); michael@0: draw.writable()->fMatrix = &matrix; michael@0: } michael@0: SkSize dstSize; michael@0: dstSize.fWidth = tmpDst.width(); michael@0: dstSize.fHeight = tmpDst.height(); michael@0: michael@0: this->drawBitmapCommon(*draw, bitmap, &tmpSrc, &dstSize, paint, flags); michael@0: } michael@0: michael@0: void SkGpuDevice::drawDevice(const SkDraw& draw, SkBaseDevice* device, michael@0: int x, int y, const SkPaint& paint) { michael@0: // clear of the source device must occur before CHECK_SHOULD_DRAW michael@0: SkGpuDevice* dev = static_cast(device); michael@0: if (dev->fNeedClear) { michael@0: // TODO: could check here whether we really need to draw at all michael@0: dev->clear(0x0); michael@0: } michael@0: michael@0: // drawDevice is defined to be in device coords. michael@0: CHECK_SHOULD_DRAW(draw, true); michael@0: michael@0: GrRenderTarget* devRT = dev->accessRenderTarget(); michael@0: GrTexture* devTex; michael@0: if (NULL == (devTex = devRT->asTexture())) { michael@0: return; michael@0: } michael@0: michael@0: const SkBitmap& bm = dev->accessBitmap(false); michael@0: int w = bm.width(); michael@0: int h = bm.height(); michael@0: michael@0: SkImageFilter* filter = paint.getImageFilter(); michael@0: // This bitmap will own the filtered result as a texture. michael@0: SkBitmap filteredBitmap; michael@0: michael@0: if (NULL != filter) { michael@0: SkIPoint offset = SkIPoint::Make(0, 0); michael@0: SkMatrix matrix(*draw.fMatrix); michael@0: matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y)); michael@0: SkIRect clipBounds = SkIRect::MakeWH(devTex->width(), devTex->height()); michael@0: SkImageFilter::Context ctx(matrix, clipBounds); michael@0: if (filter_texture(this, fContext, devTex, filter, w, h, ctx, &filteredBitmap, michael@0: &offset)) { michael@0: devTex = filteredBitmap.getTexture(); michael@0: w = filteredBitmap.width(); michael@0: h = filteredBitmap.height(); michael@0: x += offset.fX; michael@0: y += offset.fY; michael@0: } else { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: GrPaint grPaint; michael@0: grPaint.addColorTextureEffect(devTex, SkMatrix::I()); michael@0: michael@0: if (!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkRect dstRect = SkRect::MakeXYWH(SkIntToScalar(x), michael@0: SkIntToScalar(y), michael@0: SkIntToScalar(w), michael@0: SkIntToScalar(h)); michael@0: michael@0: // The device being drawn may not fill up its texture (e.g. saveLayer uses approximate michael@0: // scratch texture). michael@0: SkRect srcRect = SkRect::MakeWH(SK_Scalar1 * w / devTex->width(), michael@0: SK_Scalar1 * h / devTex->height()); michael@0: michael@0: fContext->drawRectToRect(grPaint, dstRect, srcRect); michael@0: } michael@0: michael@0: bool SkGpuDevice::canHandleImageFilter(const SkImageFilter* filter) { michael@0: return filter->canFilterImageGPU(); michael@0: } michael@0: michael@0: bool SkGpuDevice::filterImage(const SkImageFilter* filter, const SkBitmap& src, michael@0: const SkImageFilter::Context& ctx, michael@0: SkBitmap* result, SkIPoint* offset) { michael@0: // want explicitly our impl, so guard against a subclass of us overriding it michael@0: if (!this->SkGpuDevice::canHandleImageFilter(filter)) { michael@0: return false; michael@0: } michael@0: michael@0: SkAutoLockPixels alp(src, !src.getTexture()); michael@0: if (!src.getTexture() && !src.readyToDraw()) { michael@0: return false; michael@0: } michael@0: michael@0: GrTexture* texture; michael@0: // We assume here that the filter will not attempt to tile the src. Otherwise, this cache lookup michael@0: // must be pushed upstack. michael@0: SkAutoCachedTexture act(this, src, NULL, &texture); michael@0: michael@0: return filter_texture(this, fContext, texture, filter, src.width(), src.height(), ctx, michael@0: result, offset); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // must be in SkCanvas::VertexMode order michael@0: static const GrPrimitiveType gVertexMode2PrimitiveType[] = { michael@0: kTriangles_GrPrimitiveType, michael@0: kTriangleStrip_GrPrimitiveType, michael@0: kTriangleFan_GrPrimitiveType, michael@0: }; michael@0: michael@0: void SkGpuDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, michael@0: int vertexCount, const SkPoint vertices[], michael@0: const SkPoint texs[], const SkColor colors[], michael@0: SkXfermode* xmode, michael@0: const uint16_t indices[], int indexCount, michael@0: const SkPaint& paint) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: GrPaint grPaint; michael@0: // we ignore the shader if texs is null. michael@0: if (NULL == texs) { michael@0: if (!skPaint2GrPaintNoShader(this, paint, false, NULL == colors, &grPaint)) { michael@0: return; michael@0: } michael@0: } else { michael@0: if (!skPaint2GrPaintShader(this, paint, NULL == colors, &grPaint)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (NULL != xmode && NULL != texs && NULL != colors) { michael@0: if (!SkXfermode::IsMode(xmode, SkXfermode::kModulate_Mode)) { michael@0: SkDebugf("Unsupported vertex-color/texture xfer mode.\n"); michael@0: #if 0 michael@0: return michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: SkAutoSTMalloc<128, GrColor> convertedColors(0); michael@0: if (NULL != colors) { michael@0: // need to convert byte order and from non-PM to PM michael@0: convertedColors.reset(vertexCount); michael@0: for (int i = 0; i < vertexCount; ++i) { michael@0: convertedColors[i] = SkColor2GrColor(colors[i]); michael@0: } michael@0: colors = convertedColors.get(); michael@0: } michael@0: fContext->drawVertices(grPaint, michael@0: gVertexMode2PrimitiveType[vmode], michael@0: vertexCount, michael@0: (GrPoint*) vertices, michael@0: (GrPoint*) texs, michael@0: colors, michael@0: indices, michael@0: indexCount); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkGpuDevice::drawText(const SkDraw& draw, const void* text, michael@0: size_t byteLength, SkScalar x, SkScalar y, michael@0: const SkPaint& paint) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: if (fMainTextContext->canDraw(paint)) { michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: fMainTextContext->drawText(grPaint, paint, (const char *)text, byteLength, x, y); michael@0: } else if (fFallbackTextContext && fFallbackTextContext->canDraw(paint)) { michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: fFallbackTextContext->drawText(grPaint, paint, (const char *)text, byteLength, x, y); michael@0: } else { michael@0: // this guy will just call our drawPath() michael@0: draw.drawText_asPaths((const char*)text, byteLength, x, y, paint); michael@0: } michael@0: } michael@0: michael@0: void SkGpuDevice::drawPosText(const SkDraw& draw, const void* text, michael@0: size_t byteLength, const SkScalar pos[], michael@0: SkScalar constY, int scalarsPerPos, michael@0: const SkPaint& paint) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: if (fMainTextContext->canDraw(paint)) { michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: fMainTextContext->drawPosText(grPaint, paint, (const char *)text, byteLength, pos, michael@0: constY, scalarsPerPos); michael@0: } else if (fFallbackTextContext && fFallbackTextContext->canDraw(paint)) { michael@0: GrPaint grPaint; michael@0: if (!skPaint2GrPaintShader(this, paint, true, &grPaint)) { michael@0: return; michael@0: } michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: fFallbackTextContext->drawPosText(grPaint, paint, (const char *)text, byteLength, pos, michael@0: constY, scalarsPerPos); michael@0: } else { michael@0: draw.drawPosText_asPaths((const char*)text, byteLength, pos, constY, michael@0: scalarsPerPos, paint); michael@0: } michael@0: } michael@0: michael@0: void SkGpuDevice::drawTextOnPath(const SkDraw& draw, const void* text, michael@0: size_t len, const SkPath& path, michael@0: const SkMatrix* m, const SkPaint& paint) { michael@0: CHECK_SHOULD_DRAW(draw, false); michael@0: michael@0: SkASSERT(draw.fDevice == this); michael@0: draw.drawTextOnPath((const char*)text, len, path, m, paint); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: bool SkGpuDevice::filterTextFlags(const SkPaint& paint, TextFlags* flags) { michael@0: if (!paint.isLCDRenderText()) { michael@0: // we're cool with the paint as is michael@0: return false; michael@0: } michael@0: michael@0: if (paint.getShader() || michael@0: paint.getXfermode() || // unless its srcover michael@0: paint.getMaskFilter() || michael@0: paint.getRasterizer() || michael@0: paint.getColorFilter() || michael@0: paint.getPathEffect() || michael@0: paint.isFakeBoldText() || michael@0: paint.getStyle() != SkPaint::kFill_Style) { michael@0: // turn off lcd michael@0: flags->fFlags = paint.getFlags() & ~SkPaint::kLCDRenderText_Flag; michael@0: flags->fHinting = paint.getHinting(); michael@0: return true; michael@0: } michael@0: // we're cool with the paint as is michael@0: return false; michael@0: } michael@0: michael@0: void SkGpuDevice::flush() { michael@0: DO_DEFERRED_CLEAR(); michael@0: fContext->resolveRenderTarget(fRenderTarget); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: SkBaseDevice* SkGpuDevice::onCreateDevice(const SkImageInfo& info, Usage usage) { michael@0: GrTextureDesc desc; michael@0: desc.fConfig = fRenderTarget->config(); michael@0: desc.fFlags = kRenderTarget_GrTextureFlagBit; michael@0: desc.fWidth = info.width(); michael@0: desc.fHeight = info.height(); michael@0: desc.fSampleCnt = fRenderTarget->numSamples(); michael@0: michael@0: SkAutoTUnref texture; michael@0: // Skia's convention is to only clear a device if it is non-opaque. michael@0: bool needClear = !info.isOpaque(); michael@0: michael@0: #if CACHE_COMPATIBLE_DEVICE_TEXTURES michael@0: // layers are never draw in repeat modes, so we can request an approx michael@0: // match and ignore any padding. michael@0: const GrContext::ScratchTexMatch match = (kSaveLayer_Usage == usage) ? michael@0: GrContext::kApprox_ScratchTexMatch : michael@0: GrContext::kExact_ScratchTexMatch; michael@0: texture.reset(fContext->lockAndRefScratchTexture(desc, match)); michael@0: #else michael@0: texture.reset(fContext->createUncachedTexture(desc, NULL, 0)); michael@0: #endif michael@0: if (NULL != texture.get()) { michael@0: return SkNEW_ARGS(SkGpuDevice,(fContext, texture, needClear)); michael@0: } else { michael@0: GrPrintf("---- failed to create compatible device texture [%d %d]\n", michael@0: info.width(), info.height()); michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: SkSurface* SkGpuDevice::newSurface(const SkImageInfo& info) { michael@0: return SkSurface::NewRenderTarget(fContext, info, fRenderTarget->numSamples()); michael@0: } michael@0: michael@0: SkGpuDevice::SkGpuDevice(GrContext* context, michael@0: GrTexture* texture, michael@0: bool needClear) michael@0: : SkBitmapDevice(make_bitmap(context, texture->asRenderTarget())) { michael@0: michael@0: SkASSERT(texture && texture->asRenderTarget()); michael@0: // This constructor is called from onCreateDevice. It has locked the RT in the texture michael@0: // cache. We pass true for the third argument so that it will get unlocked. michael@0: this->initFromRenderTarget(context, texture->asRenderTarget(), true); michael@0: fNeedClear = needClear; michael@0: } michael@0: michael@0: class GPUAccelData : public SkPicture::AccelData { michael@0: public: michael@0: GPUAccelData(Key key) : INHERITED(key) { } michael@0: michael@0: protected: michael@0: michael@0: private: michael@0: typedef SkPicture::AccelData INHERITED; michael@0: }; michael@0: michael@0: // In the future this may not be a static method if we need to incorporate the michael@0: // clip and matrix state into the key michael@0: SkPicture::AccelData::Key SkGpuDevice::ComputeAccelDataKey() { michael@0: static const SkPicture::AccelData::Key gGPUID = SkPicture::AccelData::GenerateDomain(); michael@0: michael@0: return gGPUID; michael@0: } michael@0: michael@0: void SkGpuDevice::EXPERIMENTAL_optimize(SkPicture* picture) { michael@0: SkPicture::AccelData::Key key = ComputeAccelDataKey(); michael@0: michael@0: GPUAccelData* data = SkNEW_ARGS(GPUAccelData, (key)); michael@0: michael@0: picture->EXPERIMENTAL_addAccelData(data); michael@0: } michael@0: michael@0: bool SkGpuDevice::EXPERIMENTAL_drawPicture(const SkPicture& picture) { michael@0: SkPicture::AccelData::Key key = ComputeAccelDataKey(); michael@0: michael@0: const SkPicture::AccelData* data = picture.EXPERIMENTAL_getAccelData(key); michael@0: if (NULL == data) { michael@0: return false; michael@0: } michael@0: michael@0: #if 0 michael@0: const GPUAccelData *gpuData = static_cast(data); michael@0: #endif michael@0: michael@0: return false; michael@0: }