//////////////////////////////////////////////////////////////////////////////// // sort out what kind of clip mask needs to be created: alpha, stencil, // scissor, or entirely software bool GrClipMaskManager::setupClipping(GrPipelineBuilder* pipelineBuilder, GrPipelineBuilder::AutoRestoreFragmentProcessors* arfp, GrPipelineBuilder::AutoRestoreStencil* ars, GrScissorState* scissorState, const SkRect* devBounds) { fCurrClipMaskType = kNone_ClipMaskType; if (kRespectClip_StencilClipMode == fClipMode) { fClipMode = kIgnoreClip_StencilClipMode; } GrReducedClip::ElementList elements(16); int32_t genID = 0; GrReducedClip::InitialState initialState = GrReducedClip::kAllIn_InitialState; SkIRect clipSpaceIBounds; bool requiresAA = false; GrRenderTarget* rt = pipelineBuilder->getRenderTarget(); // GrDrawTarget should have filtered this for us SkASSERT(rt); SkIRect clipSpaceRTIBounds = SkIRect::MakeWH(rt->width(), rt->height()); const GrClip& clip = pipelineBuilder->clip(); if (clip.isWideOpen(clipSpaceRTIBounds)) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // The clip mask manager always draws with a single IRect so we special case that logic here // Image filters just use a rect, so we also special case that logic switch (clip.clipType()) { case GrClip::kWideOpen_ClipType: SkFAIL("Should have caught this with clip.isWideOpen()"); return true; case GrClip::kIRect_ClipType: { SkIRect scissor = clip.irect(); if (scissor.intersect(clipSpaceRTIBounds)) { scissorState->set(scissor); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } return false; } case GrClip::kClipStack_ClipType: { clipSpaceRTIBounds.offset(clip.origin()); GrReducedClip::ReduceClipStack(*clip.clipStack(), clipSpaceRTIBounds, &elements, &genID, &initialState, &clipSpaceIBounds, &requiresAA); if (elements.isEmpty()) { if (GrReducedClip::kAllIn_InitialState == initialState) { if (clipSpaceIBounds == clipSpaceRTIBounds) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } } else { return false; } } } break; } // An element count of 4 was chosen because of the common pattern in Blink of: // isect RR // diff RR // isect convex_poly // isect convex_poly // when drawing rounded div borders. This could probably be tuned based on a // configuration's relative costs of switching RTs to generate a mask vs // longer shaders. if (elements.count() <= 4) { SkVector clipToRTOffset = { SkIntToScalar(-clip.origin().fX), SkIntToScalar(-clip.origin().fY) }; if (elements.isEmpty() || (requiresAA && this->installClipEffects(pipelineBuilder, arfp, elements, clipToRTOffset, devBounds))) { SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(-clip.origin()); if (NULL == devBounds || !SkRect::Make(scissorSpaceIBounds).contains(*devBounds)) { scissorState->set(scissorSpaceIBounds); } this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } } // If MSAA is enabled we can do everything in the stencil buffer. if (0 == rt->numSamples() && requiresAA) { GrTexture* result = NULL; // The top-left of the mask corresponds to the top-left corner of the bounds. SkVector clipToMaskOffset = { SkIntToScalar(-clipSpaceIBounds.fLeft), SkIntToScalar(-clipSpaceIBounds.fTop) }; if (this->useSWOnlyPath(pipelineBuilder, clipToMaskOffset, elements)) { // The clip geometry is complex enough that it will be more efficient to create it // entirely in software result = this->createSoftwareClipMask(genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds); } else { result = this->createAlphaClipMask(genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds); } if (result) { arfp->set(pipelineBuilder); // The mask's top left coord should be pinned to the rounded-out top left corner of // clipSpace bounds. We determine the mask's position WRT to the render target here. SkIRect rtSpaceMaskBounds = clipSpaceIBounds; rtSpaceMaskBounds.offset(-clip.origin()); setup_drawstate_aaclip(pipelineBuilder, result, arfp, rtSpaceMaskBounds); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // if alpha clip mask creation fails fall through to the non-AA code paths } // Either a hard (stencil buffer) clip was explicitly requested or an anti-aliased clip couldn't // be created. In either case, free up the texture in the anti-aliased mask cache. // TODO: this may require more investigation. Ganesh performs a lot of utility draws (e.g., // clears, InOrderDrawBuffer playbacks) that hit the stencil buffer path. These may be // "incorrectly" clearing the AA cache. fAACache.reset(); // use the stencil clip if we can't represent the clip as a rectangle. SkIPoint clipSpaceToStencilSpaceOffset = -clip.origin(); this->createStencilClipMask(rt, genID, initialState, elements, clipSpaceIBounds, clipSpaceToStencilSpaceOffset); // This must occur after createStencilClipMask. That function may change the scissor. Also, it // only guarantees that the stencil mask is correct within the bounds it was passed, so we must // use both stencil and scissor test to the bounds for the final draw. SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset); scissorState->set(scissorSpaceIBounds); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; }
bool GrDrawTarget::setupDstReadIfNecessary(const GrPipelineBuilder& pipelineBuilder, const GrProcOptInfo& colorPOI, const GrProcOptInfo& coveragePOI, GrXferProcessor::DstTexture* dstTexture, const SkRect* drawBounds) { if (!pipelineBuilder.willXPNeedDstTexture(*this->caps(), colorPOI, coveragePOI)) { return true; } GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); if (this->caps()->textureBarrierSupport()) { if (GrTexture* rtTex = rt->asTexture()) { // The render target is a texture, so we can read from it directly in the shader. The XP // will be responsible to detect this situation and request a texture barrier. dstTexture->setTexture(rtTex); dstTexture->setOffset(0, 0); return true; } } SkIRect copyRect; pipelineBuilder.clip().getConservativeBounds(rt, ©Rect); if (drawBounds) { SkIRect drawIBounds; drawBounds->roundOut(&drawIBounds); if (!copyRect.intersect(drawIBounds)) { #ifdef SK_DEBUG GrCapsDebugf(fCaps, "Missed an early reject. " "Bailing on draw from setupDstReadIfNecessary.\n"); #endif return false; } } else { #ifdef SK_DEBUG //SkDebugf("No dev bounds when dst copy is made.\n"); #endif } // MSAA consideration: When there is support for reading MSAA samples in the shader we could // have per-sample dst values by making the copy multisampled. GrSurfaceDesc desc; if (!this->getGpu()->initCopySurfaceDstDesc(rt, &desc)) { desc.fOrigin = kDefault_GrSurfaceOrigin; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fConfig = rt->config(); } desc.fWidth = copyRect.width(); desc.fHeight = copyRect.height(); SkAutoTUnref<GrTexture> copy( fResourceProvider->refScratchTexture(desc, GrTextureProvider::kApprox_ScratchTexMatch)); if (!copy) { SkDebugf("Failed to create temporary copy of destination texture.\n"); return false; } SkIPoint dstPoint = {0, 0}; this->copySurface(copy, rt, copyRect, dstPoint); dstTexture->setTexture(copy); dstTexture->setOffset(copyRect.fLeft, copyRect.fTop); return true; }
void draw(SkCanvas* canvas) { SkIRect result; bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 }); SkDebugf("%s intersection: %d, %d, %d, %d\n", intersected ? "" : "no ", result.left(), result.top(), result.right(), result.bottom()); }
static void blitClippedRect(SkBlitter* blitter, const SkIRect& rect, const SkIRect& clipR) { SkIRect r; if (r.intersect(rect, clipR)) { blitter->blitRect(r.left(), r.top(), r.width(), r.height()); } }
static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR, const SkIPoint& center, bool fillCenter, const SkIRect& clipR, SkBlitter* blitter) { int cx = center.x(); int cy = center.y(); SkMask m; // top-left m.fBounds = mask.fBounds; m.fBounds.fRight = cx; m.fBounds.fBottom = cy; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.left(), outerR.top()); blitClippedMask(blitter, m, m.fBounds, clipR); } // top-right m.fBounds = mask.fBounds; m.fBounds.fLeft = cx + 1; m.fBounds.fBottom = cy; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.top()); blitClippedMask(blitter, m, m.fBounds, clipR); } // bottom-left m.fBounds = mask.fBounds; m.fBounds.fRight = cx; m.fBounds.fTop = cy + 1; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.left(), outerR.bottom() - m.fBounds.height()); blitClippedMask(blitter, m, m.fBounds, clipR); } // bottom-right m.fBounds = mask.fBounds; m.fBounds.fLeft = cx + 1; m.fBounds.fTop = cy + 1; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.bottom() - m.fBounds.height()); blitClippedMask(blitter, m, m.fBounds, clipR); } SkIRect innerR; innerR.set(outerR.left() + cx - mask.fBounds.left(), outerR.top() + cy - mask.fBounds.top(), outerR.right() + (cx + 1 - mask.fBounds.right()), outerR.bottom() + (cy + 1 - mask.fBounds.bottom())); if (fillCenter) { blitClippedRect(blitter, innerR, clipR); } const int innerW = innerR.width(); size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t)); SkAutoSMalloc<4*1024> storage(storageSize); int16_t* runs = (int16_t*)storage.get(); uint8_t* alpha = (uint8_t*)(runs + innerW + 1); SkIRect r; // top r.set(innerR.left(), outerR.top(), innerR.right(), innerR.top()); if (r.intersect(clipR)) { int startY = SkMax32(0, r.top() - outerR.top()); int stopY = startY + r.height(); int width = r.width(); for (int y = startY; y < stopY; ++y) { runs[0] = width; runs[width] = 0; alpha[0] = *mask.getAddr8(cx, mask.fBounds.top() + y); blitter->blitAntiH(r.left(), outerR.top() + y, alpha, runs); } } // bottom r.set(innerR.left(), innerR.bottom(), innerR.right(), outerR.bottom()); if (r.intersect(clipR)) { int startY = outerR.bottom() - r.bottom(); int stopY = startY + r.height(); int width = r.width(); for (int y = startY; y < stopY; ++y) { runs[0] = width; runs[width] = 0; alpha[0] = *mask.getAddr8(cx, mask.fBounds.bottom() - y - 1); blitter->blitAntiH(r.left(), outerR.bottom() - y - 1, alpha, runs); } } // left r.set(outerR.left(), innerR.top(), innerR.left(), innerR.bottom()); if (r.intersect(clipR)) { int startX = r.left() - outerR.left(); int stopX = startX + r.width(); int height = r.height(); for (int x = startX; x < stopX; ++x) { blitter->blitV(outerR.left() + x, r.top(), height, *mask.getAddr8(mask.fBounds.left() + x, mask.fBounds.top() + cy)); } } // right r.set(innerR.right(), innerR.top(), outerR.right(), innerR.bottom()); if (r.intersect(clipR)) { int startX = outerR.right() - r.right(); int stopX = startX + r.width(); int height = r.height(); for (int x = startX; x < stopX; ++x) { blitter->blitV(outerR.right() - x - 1, r.top(), height, *mask.getAddr8(mask.fBounds.right() - x - 1, mask.fBounds.top() + cy)); } } }
void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder, const GrClip& clip, GrDrawBatch* batch) { // Setup clip GrAppliedClip appliedClip; if (!clip.apply(fClipMaskManager, pipelineBuilder, &batch->bounds(), &appliedClip)) { return; } GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps; if (appliedClip.clipCoverageFragmentProcessor()) { arfps.set(&pipelineBuilder); arfps.addCoverageFragmentProcessor(appliedClip.clipCoverageFragmentProcessor()); } GrPipeline::CreateArgs args; args.fPipelineBuilder = &pipelineBuilder; args.fCaps = this->caps(); args.fScissor = &appliedClip.scissorState(); args.fHasStencilClip = appliedClip.hasStencilClip(); if (pipelineBuilder.hasUserStencilSettings() || appliedClip.hasStencilClip()) { if (!fResourceProvider->attachStencilAttachment(pipelineBuilder.getRenderTarget())) { SkDebugf("ERROR creating stencil attachment. Draw skipped.\n"); return; } } batch->getPipelineOptimizations(&args.fOpts); GrScissorState finalScissor; if (args.fOpts.fOverrides.fUsePLSDstRead || fClipBatchToBounds) { GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); GrGLIRect viewport; viewport.fLeft = 0; viewport.fBottom = 0; viewport.fWidth = rt->width(); viewport.fHeight = rt->height(); SkIRect ibounds; ibounds.fLeft = SkTPin(SkScalarFloorToInt(batch->bounds().fLeft), viewport.fLeft, viewport.fWidth); ibounds.fTop = SkTPin(SkScalarFloorToInt(batch->bounds().fTop), viewport.fBottom, viewport.fHeight); ibounds.fRight = SkTPin(SkScalarCeilToInt(batch->bounds().fRight), viewport.fLeft, viewport.fWidth); ibounds.fBottom = SkTPin(SkScalarCeilToInt(batch->bounds().fBottom), viewport.fBottom, viewport.fHeight); if (appliedClip.scissorState().enabled()) { const SkIRect& scissorRect = appliedClip.scissorState().rect(); if (!ibounds.intersect(scissorRect)) { return; } } finalScissor.set(ibounds); args.fScissor = &finalScissor; } args.fOpts.fColorPOI.completeCalculations(pipelineBuilder.fColorFragmentProcessors.begin(), pipelineBuilder.numColorFragmentProcessors()); args.fOpts.fCoveragePOI.completeCalculations( pipelineBuilder.fCoverageFragmentProcessors.begin(), pipelineBuilder.numCoverageFragmentProcessors()); if (!this->setupDstReadIfNecessary(pipelineBuilder, clip, args.fOpts, &args.fDstTexture, batch->bounds())) { return; } if (!batch->installPipeline(args)) { return; } #ifdef ENABLE_MDB SkASSERT(fRenderTarget); batch->pipeline()->addDependenciesTo(fRenderTarget); #endif this->recordBatch(batch); }
//////////////////////////////////////////////////////////////////////////////// // sort out what kind of clip mask needs to be created: alpha, stencil, // scissor, or entirely software bool GrClipMaskManager::setupClipping(const GrPipelineBuilder& pipelineBuilder, GrPipelineBuilder::AutoRestoreStencil* ars, const SkRect* devBounds, GrAppliedClip* out) { if (kRespectClip_StencilClipMode == fClipMode) { fClipMode = kIgnoreClip_StencilClipMode; } GrReducedClip::ElementList elements; int32_t genID = 0; GrReducedClip::InitialState initialState = GrReducedClip::kAllIn_InitialState; SkIRect clipSpaceIBounds; bool requiresAA = false; GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); // GrDrawTarget should have filtered this for us SkASSERT(rt); SkIRect clipSpaceRTIBounds = SkIRect::MakeWH(rt->width(), rt->height()); GrClip devBoundsClip; bool doDevBoundsClip = fDebugClipBatchToBounds && devBounds; if (doDevBoundsClip) { add_rect_to_clip(pipelineBuilder.clip(), *devBounds, &devBoundsClip); } const GrClip& clip = doDevBoundsClip ? devBoundsClip : pipelineBuilder.clip(); if (clip.isWideOpen(clipSpaceRTIBounds)) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // The clip mask manager always draws with a single IRect so we special case that logic here // Image filters just use a rect, so we also special case that logic switch (clip.clipType()) { case GrClip::kWideOpen_ClipType: SkFAIL("Should have caught this with clip.isWideOpen()"); return true; case GrClip::kIRect_ClipType: { SkIRect scissor = clip.irect(); if (scissor.intersect(clipSpaceRTIBounds)) { out->fScissorState.set(scissor); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } return false; } case GrClip::kClipStack_ClipType: { clipSpaceRTIBounds.offset(clip.origin()); SkIRect clipSpaceReduceQueryBounds; #define DISABLE_DEV_BOUNDS_FOR_CLIP_REDUCTION 0 if (devBounds && !DISABLE_DEV_BOUNDS_FOR_CLIP_REDUCTION) { SkIRect devIBounds = devBounds->roundOut(); devIBounds.offset(clip.origin()); if (!clipSpaceReduceQueryBounds.intersect(clipSpaceRTIBounds, devIBounds)) { return false; } } else { clipSpaceReduceQueryBounds = clipSpaceRTIBounds; } GrReducedClip::ReduceClipStack(*clip.clipStack(), clipSpaceReduceQueryBounds, &elements, &genID, &initialState, &clipSpaceIBounds, &requiresAA); if (elements.isEmpty()) { if (GrReducedClip::kAllIn_InitialState == initialState) { if (clipSpaceIBounds == clipSpaceRTIBounds) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } } else { return false; } } } break; } // An element count of 4 was chosen because of the common pattern in Blink of: // isect RR // diff RR // isect convex_poly // isect convex_poly // when drawing rounded div borders. This could probably be tuned based on a // configuration's relative costs of switching RTs to generate a mask vs // longer shaders. if (elements.count() <= kMaxAnalyticElements) { SkVector clipToRTOffset = { SkIntToScalar(-clip.origin().fX), SkIntToScalar(-clip.origin().fY) }; // When there are multiple samples we want to do per-sample clipping, not compute a // fractional pixel coverage. bool disallowAnalyticAA = rt->isUnifiedMultisampled() || pipelineBuilder.hasMixedSamples(); const GrFragmentProcessor* clipFP = nullptr; if (elements.isEmpty() || (requiresAA && this->getAnalyticClipProcessor(elements, disallowAnalyticAA, clipToRTOffset, devBounds, &clipFP))) { SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(-clip.origin()); if (nullptr == devBounds || !SkRect::Make(scissorSpaceIBounds).contains(*devBounds)) { out->fScissorState.set(scissorSpaceIBounds); } this->setPipelineBuilderStencil(pipelineBuilder, ars); out->fClipCoverageFP.reset(clipFP); return true; } } // If the stencil buffer is multisampled we can use it to do everything. if (!rt->isStencilBufferMultisampled() && requiresAA) { SkAutoTUnref<GrTexture> result; // The top-left of the mask corresponds to the top-left corner of the bounds. SkVector clipToMaskOffset = { SkIntToScalar(-clipSpaceIBounds.fLeft), SkIntToScalar(-clipSpaceIBounds.fTop) }; if (UseSWOnlyPath(this->getContext(), pipelineBuilder, rt, clipToMaskOffset, elements)) { // The clip geometry is complex enough that it will be more efficient to create it // entirely in software result.reset(CreateSoftwareClipMask(this->getContext(), genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds)); } else { result.reset(CreateAlphaClipMask(this->getContext(), genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds)); // If createAlphaClipMask fails it means UseSWOnlyPath has a bug SkASSERT(result); } if (result) { // The mask's top left coord should be pinned to the rounded-out top left corner of // clipSpace bounds. We determine the mask's position WRT to the render target here. SkIRect rtSpaceMaskBounds = clipSpaceIBounds; rtSpaceMaskBounds.offset(-clip.origin()); out->fClipCoverageFP.reset(create_fp_for_mask(result, rtSpaceMaskBounds)); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // if alpha clip mask creation fails fall through to the non-AA code paths } // use the stencil clip if we can't represent the clip as a rectangle. SkIPoint clipSpaceToStencilSpaceOffset = -clip.origin(); this->createStencilClipMask(rt, genID, initialState, elements, clipSpaceIBounds, clipSpaceToStencilSpaceOffset); // This must occur after createStencilClipMask. That function may change the scissor. Also, it // only guarantees that the stencil mask is correct within the bounds it was passed, so we must // use both stencil and scissor test to the bounds for the final draw. SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset); out->fScissorState.set(scissorSpaceIBounds); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; }
bool SkDisplacementMapEffect::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx, SkBitmap* result, SkIPoint* offset) const { SkBitmap colorBM = src; SkIPoint colorOffset = SkIPoint::Make(0, 0); if (getColorInput() && !getColorInput()->getInputResultGPU(proxy, src, ctx, &colorBM, &colorOffset)) { return false; } SkBitmap displacementBM = src; SkIPoint displacementOffset = SkIPoint::Make(0, 0); if (getDisplacementInput() && !getDisplacementInput()->getInputResultGPU(proxy, src, ctx, &displacementBM, &displacementOffset)) { return false; } SkIRect bounds; // Since GrDisplacementMapEffect does bounds checking on color pixel access, we don't need to // pad the color bitmap to bounds here. if (!this->applyCropRect(ctx, colorBM, colorOffset, &bounds)) { return false; } SkIRect displBounds; if (!this->applyCropRect(ctx, proxy, displacementBM, &displacementOffset, &displBounds, &displacementBM)) { return false; } if (!bounds.intersect(displBounds)) { return false; } GrTexture* color = colorBM.getTexture(); GrTexture* displacement = displacementBM.getTexture(); GrContext* context = color->getContext(); GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = bounds.width(); desc.fHeight = bounds.height(); desc.fConfig = kSkia8888_GrPixelConfig; SkAutoTUnref<GrTexture> dst(context->textureProvider()->refScratchTexture(desc, GrTextureProvider::kApprox_ScratchTexMatch)); if (!dst) { return false; } SkVector scale = SkVector::Make(fScale, fScale); ctx.ctm().mapVectors(&scale, 1); GrPaint paint; SkMatrix offsetMatrix = GrCoordTransform::MakeDivByTextureWHMatrix(displacement); offsetMatrix.preTranslate(SkIntToScalar(colorOffset.fX - displacementOffset.fX), SkIntToScalar(colorOffset.fY - displacementOffset.fY)); paint.addColorProcessor( GrDisplacementMapEffect::Create(fXChannelSelector, fYChannelSelector, scale, displacement, offsetMatrix, color, colorBM.dimensions()))->unref(); SkIRect colorBounds = bounds; colorBounds.offset(-colorOffset); SkMatrix matrix; matrix.setTranslate(-SkIntToScalar(colorBounds.x()), -SkIntToScalar(colorBounds.y())); context->drawRect(dst->asRenderTarget(), GrClip::WideOpen(), paint, matrix, SkRect::Make(colorBounds)); offset->fX = bounds.left(); offset->fY = bounds.top(); WrapTexture(dst, bounds.width(), bounds.height(), result); return true; }
bool GrDrawTarget::setupDstReadIfNecessary(const GrPipelineBuilder& pipelineBuilder, const GrClip& clip, const GrPipelineOptimizations& optimizations, GrXferProcessor::DstTexture* dstTexture, const SkRect& batchBounds) { SkRect bounds = batchBounds; bounds.outset(0.5f, 0.5f); if (!pipelineBuilder.willXPNeedDstTexture(*this->caps(), optimizations)) { return true; } GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); if (this->caps()->textureBarrierSupport()) { if (GrTexture* rtTex = rt->asTexture()) { // The render target is a texture, so we can read from it directly in the shader. The XP // will be responsible to detect this situation and request a texture barrier. dstTexture->setTexture(rtTex); dstTexture->setOffset(0, 0); return true; } } SkIRect copyRect; clip.getConservativeBounds(rt->width(), rt->height(), ©Rect); SkIRect drawIBounds; bounds.roundOut(&drawIBounds); if (!copyRect.intersect(drawIBounds)) { #ifdef SK_DEBUG GrCapsDebugf(this->caps(), "Missed an early reject. " "Bailing on draw from setupDstReadIfNecessary.\n"); #endif return false; } // MSAA consideration: When there is support for reading MSAA samples in the shader we could // have per-sample dst values by making the copy multisampled. GrSurfaceDesc desc; if (!fGpu->initCopySurfaceDstDesc(rt, &desc)) { desc.fOrigin = kDefault_GrSurfaceOrigin; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fConfig = rt->config(); } desc.fWidth = copyRect.width(); desc.fHeight = copyRect.height(); static const uint32_t kFlags = 0; SkAutoTUnref<GrTexture> copy(fResourceProvider->createApproxTexture(desc, kFlags)); if (!copy) { SkDebugf("Failed to create temporary copy of destination texture.\n"); return false; } SkIPoint dstPoint = {0, 0}; this->copySurface(copy, rt, copyRect, dstPoint); dstTexture->setTexture(copy); dstTexture->setOffset(copyRect.fLeft, copyRect.fTop); return true; }
bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, SkIRect region) { if (index == NULL) { return false; } jpeg_decompress_struct *cinfo = index->cinfo; SkIRect rect = SkIRect::MakeWH(this->imageWidth, this->imageHeight); if (!rect.intersect(region)) { // If the requested region is entirely outsides the image, just // returns false return false; } SkAutoMalloc srcStorage; skjpeg_error_mgr sk_err; cinfo->err = jpeg_std_error(&sk_err); sk_err.error_exit = skjpeg_error_exit; if (setjmp(sk_err.fJmpBuf)) { return false; } int requestedSampleSize = this->getSampleSize(); cinfo->scale_denom = requestedSampleSize; if (this->getPreferQualityOverSpeed()) { cinfo->dct_method = JDCT_ISLOW; } else { cinfo->dct_method = JDCT_IFAST; } SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false); if (config != SkBitmap::kARGB_8888_Config && config != SkBitmap::kARGB_4444_Config && config != SkBitmap::kRGB_565_Config) { config = SkBitmap::kARGB_8888_Config; } /* default format is RGB */ cinfo->out_color_space = JCS_RGB; #ifdef ANDROID_RGB cinfo->dither_mode = JDITHER_NONE; if (config == SkBitmap::kARGB_8888_Config) { cinfo->out_color_space = JCS_RGBA_8888; } else if (config == SkBitmap::kRGB_565_Config) { cinfo->out_color_space = JCS_RGB_565; if (this->getDitherImage()) { cinfo->dither_mode = JDITHER_ORDERED; } } #endif int startX = rect.fLeft; int startY = rect.fTop; int width = rect.width(); int height = rect.height(); jpeg_init_read_tile_scanline(cinfo, index->index, &startX, &startY, &width, &height); int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo); int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size); SkBitmap *bitmap = new SkBitmap; SkAutoTDelete<SkBitmap> adb(bitmap); #ifdef ANDROID_RGB /* short-circuit the SkScaledBitmapSampler when possible, as this gives a significant performance boost. */ if (skiaSampleSize == 1 && ((config == SkBitmap::kARGB_8888_Config && cinfo->out_color_space == JCS_RGBA_8888) || (config == SkBitmap::kRGB_565_Config && cinfo->out_color_space == JCS_RGB_565))) { bitmap->setConfig(config, cinfo->output_width, height); bitmap->setIsOpaque(true); // Check ahead of time if the swap(dest, src) is possible or not. // If yes, then we will stick to AllocPixelRef since it's cheaper // with the swap happening. If no, then we will use alloc to allocate // pixels to prevent garbage collection. // // Not using a recycled-bitmap and the output rect is same as the // decoded region. int w = rect.width() / actualSampleSize; int h = rect.height() / actualSampleSize; bool swapOnly = (rect == region) && bm->isNull() && (w == bitmap->width()) && (h == bitmap->height()) && ((startX - rect.x()) / actualSampleSize == 0) && ((startY - rect.y()) / actualSampleSize == 0); if (swapOnly) { if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } } else { if (!bitmap->allocPixels()) { return return_false(*cinfo, *bitmap, "allocPixels"); } } SkAutoLockPixels alp(*bitmap); JSAMPLE* rowptr = (JSAMPLE*)bitmap->getPixels(); INT32 const bpr = bitmap->rowBytes(); int row_total_count = 0; while (row_total_count < height) { int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); // if row_count == 0, then we didn't get a scanline, so abort. // if we supported partial images, we might return true in this case if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } row_total_count += row_count; rowptr += bpr; } if (swapOnly) { bm->swap(*bitmap); } else { cropBitmap(bm, bitmap, actualSampleSize, region.x(), region.y(), region.width(), region.height(), startX, startY); } return true; } #endif // check for supported formats SkScaledBitmapSampler::SrcConfig sc; if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB; #ifdef ANDROID_RGB } else if (JCS_RGBA_8888 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGBX; } else if (JCS_RGB_565 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB_565; #endif } else if (1 == cinfo->out_color_components && JCS_GRAYSCALE == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kGray; } else { return return_false(*cinfo, *bm, "jpeg colorspace"); } SkScaledBitmapSampler sampler(width, height, skiaSampleSize); bitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); bitmap->setIsOpaque(true); // Check ahead of time if the swap(dest, src) is possible or not. // If yes, then we will stick to AllocPixelRef since it's cheaper with the // swap happening. If no, then we will use alloc to allocate pixels to // prevent garbage collection. int w = rect.width() / actualSampleSize; int h = rect.height() / actualSampleSize; bool swapOnly = (rect == region) && bm->isNull() && (w == bitmap->width()) && (h == bitmap->height()) && ((startX - rect.x()) / actualSampleSize == 0) && ((startY - rect.y()) / actualSampleSize == 0); if (swapOnly) { if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } } else { if (!bitmap->allocPixels()) { return return_false(*cinfo, *bitmap, "allocPixels"); } } SkAutoLockPixels alp(*bitmap); if (!sampler.begin(bitmap, sc, this->getDitherImage())) { return return_false(*cinfo, *bitmap, "sampler.begin"); } uint8_t* srcRow = (uint8_t*)srcStorage.reset(width * 4); // Possibly skip initial rows [sampler.srcY0] if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcY0())) { return return_false(*cinfo, *bitmap, "skip rows"); } // now loop through scanlines until y == bitmap->height() - 1 for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } sampler.next(srcRow); if (bitmap->height() - 1 == y) { // we're done break; } if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcDY() - 1)) { return return_false(*cinfo, *bitmap, "skip rows"); } } if (swapOnly) { bm->swap(*bitmap); } else { cropBitmap(bm, bitmap, actualSampleSize, region.x(), region.y(), region.width(), region.height(), startX, startY); } return true; }
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(CopySurface, reporter, ctxInfo) { GrContext* context = ctxInfo.grContext(); static const int kW = 10; static const int kH = 10; static const size_t kRowBytes = sizeof(uint32_t) * kW; GrSurfaceDesc baseDesc; baseDesc.fConfig = kRGBA_8888_GrPixelConfig; baseDesc.fWidth = kW; baseDesc.fHeight = kH; SkAutoTMalloc<uint32_t> srcPixels(kW * kH); for (int i = 0; i < kW * kH; ++i) { srcPixels.get()[i] = i; } SkAutoTMalloc<uint32_t> dstPixels(kW * kH); for (int i = 0; i < kW * kH; ++i) { dstPixels.get()[i] = ~i; } static const SkIRect kSrcRects[] { { 0, 0, kW , kH }, {-1, -1, kW+1, kH+1}, { 1, 1, kW-1, kH-1}, { 5, 5, 6 , 6 }, }; static const SkIPoint kDstPoints[] { { 0 , 0 }, { 1 , 1 }, { kW/2, kH/4}, { kW-1, kH-1}, { kW , kH }, { kW+1, kH+2}, {-1 , -1 }, }; SkAutoTMalloc<uint32_t> read(kW * kH); for (auto sOrigin : {kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin}) { for (auto dOrigin : {kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin}) { for (auto sFlags: {kRenderTarget_GrSurfaceFlag, kNone_GrSurfaceFlags}) { for (auto dFlags: {kRenderTarget_GrSurfaceFlag, kNone_GrSurfaceFlags}) { for (auto srcRect : kSrcRects) { for (auto dstPoint : kDstPoints) { GrSurfaceDesc srcDesc = baseDesc; srcDesc.fOrigin = sOrigin; srcDesc.fFlags = sFlags; GrSurfaceDesc dstDesc = baseDesc; dstDesc.fOrigin = dOrigin; dstDesc.fFlags = dFlags; SkAutoTUnref<GrTexture> src( context->textureProvider()->createTexture(srcDesc, SkBudgeted::kNo, srcPixels.get(), kRowBytes)); SkAutoTUnref<GrTexture> dst( context->textureProvider()->createTexture(dstDesc, SkBudgeted::kNo, dstPixels.get(), kRowBytes)); if (!src || !dst) { ERRORF(reporter, "Could not create surfaces for copy surface test."); continue; } bool result = context->copySurface(dst, src, srcRect, dstPoint); bool expectedResult = true; SkIPoint dstOffset = { dstPoint.fX - srcRect.fLeft, dstPoint.fY - srcRect.fTop }; SkIRect copiedDstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY, srcRect.width(), srcRect.height()); SkIRect copiedSrcRect; if (!copiedSrcRect.intersect(srcRect, SkIRect::MakeWH(kW, kH))) { expectedResult = false; } else { // If the src rect was clipped, apply same clipping to each side of // copied dst rect. copiedDstRect.fLeft += copiedSrcRect.fLeft - srcRect.fLeft; copiedDstRect.fTop += copiedSrcRect.fTop - srcRect.fTop; copiedDstRect.fRight -= copiedSrcRect.fRight - srcRect.fRight; copiedDstRect.fBottom -= copiedSrcRect.fBottom - srcRect.fBottom; } if (copiedDstRect.isEmpty() || !copiedDstRect.intersect(SkIRect::MakeWH(kW, kH))) { expectedResult = false; } // To make the copied src rect correct we would apply any dst clipping // back to the src rect, but we don't use it again so don't bother. if (expectedResult != result) { ERRORF(reporter, "Expected return value %d from copySurface, got " "%d.", expectedResult, result); continue; } if (!expectedResult || !result) { continue; } sk_memset32(read.get(), 0, kW * kH); if (!dst->readPixels(0, 0, kW, kH, baseDesc.fConfig, read.get(), kRowBytes)) { ERRORF(reporter, "Error calling readPixels"); continue; } bool abort = false; // Validate that pixels inside copiedDstRect received the correct value // from src and that those outside were not modified. for (int y = 0; y < kH && !abort; ++y) { for (int x = 0; x < kW; ++x) { uint32_t r = read.get()[y * kW + x]; if (copiedDstRect.contains(x, y)) { int sx = x - dstOffset.fX; int sy = y - dstOffset.fY; uint32_t s = srcPixels.get()[sy * kW + sx]; if (s != r) { ERRORF(reporter, "Expected dst %d,%d to contain " "0x%08x copied from src location %d,%d. Got " "0x%08x", x, y, s, sx, sy, r); abort = true; break; } } else { uint32_t d = dstPixels.get()[y * kW + x]; if (d != r) { ERRORF(reporter, "Expected dst %d,%d to be unmodified (" "0x%08x). Got 0x%08x", x, y, d, r); abort = true; break; } } } } } } } } } } }
bool SkBlurImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const Context& ctx, SkBitmap* dst, SkIPoint* offset) const { SkBitmap src = source; SkIPoint srcOffset = SkIPoint::Make(0, 0); if (!this->filterInput(0, proxy, source, ctx, &src, &srcOffset)) { return false; } if (src.colorType() != kN32_SkColorType) { return false; } SkIRect srcBounds = src.bounds(); srcBounds.offset(srcOffset); SkIRect dstBounds; if (!this->applyCropRect(this->mapContext(ctx), srcBounds, &dstBounds)) { return false; } if (!srcBounds.intersect(dstBounds)) { return false; } SkVector sigma = map_sigma(fSigma, ctx.ctm()); int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX; int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY; getBox3Params(sigma.x(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX); getBox3Params(sigma.y(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY); if (kernelSizeX < 0 || kernelSizeY < 0) { return false; } if (kernelSizeX == 0 && kernelSizeY == 0) { src.extractSubset(dst, srcBounds); offset->fX = srcBounds.x(); offset->fY = srcBounds.y(); return true; } SkAutoLockPixels alp(src); if (!src.getPixels()) { return false; } SkAutoTUnref<SkBaseDevice> device(proxy->createDevice(dstBounds.width(), dstBounds.height())); if (!device) { return false; } *dst = device->accessBitmap(false); SkAutoLockPixels alp_dst(*dst); SkAutoTUnref<SkBaseDevice> tempDevice(proxy->createDevice(dst->width(), dst->height())); if (!tempDevice) { return false; } SkBitmap temp = tempDevice->accessBitmap(false); SkAutoLockPixels alpTemp(temp); offset->fX = dstBounds.fLeft; offset->fY = dstBounds.fTop; SkPMColor* t = temp.getAddr32(0, 0); SkPMColor* d = dst->getAddr32(0, 0); int w = dstBounds.width(), h = dstBounds.height(); const SkPMColor* s = src.getAddr32(srcBounds.x() - srcOffset.x(), srcBounds.y() - srcOffset.y()); srcBounds.offset(-dstBounds.x(), -dstBounds.y()); dstBounds.offset(-dstBounds.x(), -dstBounds.y()); SkIRect srcBoundsT = SkIRect::MakeLTRB(srcBounds.top(), srcBounds.left(), srcBounds.bottom(), srcBounds.right()); SkIRect dstBoundsT = SkIRect::MakeWH(dstBounds.height(), dstBounds.width()); int sw = src.rowBytesAsPixels(); /** * * In order to make memory accesses cache-friendly, we reorder the passes to * use contiguous memory reads wherever possible. * * For example, the 6 passes of the X-and-Y blur case are rewritten as * follows. Instead of 3 passes in X and 3 passes in Y, we perform * 2 passes in X, 1 pass in X transposed to Y on write, 2 passes in X, * then 1 pass in X transposed to Y on write. * * +----+ +----+ +----+ +---+ +---+ +---+ +----+ * + AB + ----> | AB | ----> | AB | -----> | A | ----> | A | ----> | A | -----> | AB | * +----+ blurX +----+ blurX +----+ blurXY | B | blurX | B | blurX | B | blurXY +----+ * +---+ +---+ +---+ * * In this way, two of the y-blurs become x-blurs applied to transposed * images, and all memory reads are contiguous. */ if (kernelSizeX > 0 && kernelSizeY > 0) { SkOpts::box_blur_xx(s, sw, srcBounds, t, kernelSizeX, lowOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX, highOffsetX, lowOffsetX, w, h); SkOpts::box_blur_xy(d, w, dstBounds, t, kernelSizeX3, highOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(t, h, dstBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); } else if (kernelSizeX > 0) { SkOpts::box_blur_xx(s, sw, srcBounds, d, kernelSizeX, lowOffsetX, highOffsetX, w, h); SkOpts::box_blur_xx(d, w, dstBounds, t, kernelSizeX, highOffsetX, lowOffsetX, w, h); SkOpts::box_blur_xx(t, w, dstBounds, d, kernelSizeX3, highOffsetX, highOffsetX, w, h); } else if (kernelSizeY > 0) { SkOpts::box_blur_yx(s, sw, srcBoundsT, d, kernelSizeY, lowOffsetY, highOffsetY, h, w); SkOpts::box_blur_xx(d, h, dstBoundsT, t, kernelSizeY, highOffsetY, lowOffsetY, h, w); SkOpts::box_blur_xy(t, h, dstBoundsT, d, kernelSizeY3, highOffsetY, highOffsetY, h, w); } return true; }
void SkBitmapDevice::writePixels(const SkBitmap& bitmap, int x, int y, SkCanvas::Config8888 config8888) { if (bitmap.isNull() || bitmap.getTexture()) { return; } const SkBitmap* sprite = &bitmap; // check whether we have to handle a config8888 that doesn't match SkPMColor if (SkBitmap::kARGB_8888_Config == bitmap.config() && SkCanvas::kNative_Premul_Config8888 != config8888 && kPMColorAlias != config8888) { // We're going to have to convert from a config8888 to the native config // First we clip to the device bounds. SkBitmap dstBmp = this->accessBitmap(true); SkIRect spriteRect = SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()); SkIRect devRect = SkIRect::MakeWH(dstBmp.width(), dstBmp.height()); if (!spriteRect.intersect(devRect)) { return; } // write directly to the device if it has pixels and is SkPMColor bool drawSprite; if (SkBitmap::kARGB_8888_Config == dstBmp.config() && !dstBmp.isNull()) { // we can write directly to the dst when doing the conversion dstBmp.extractSubset(&dstBmp, spriteRect); drawSprite = false; } else { // we convert to a temporary bitmap and draw that as a sprite dstBmp.setConfig(SkBitmap::kARGB_8888_Config, spriteRect.width(), spriteRect.height()); if (!dstBmp.allocPixels()) { return; } drawSprite = true; } // copy pixels to dstBmp and convert from config8888 to native config. SkAutoLockPixels alp(bitmap); uint32_t* srcPixels = bitmap.getAddr32(spriteRect.fLeft - x, spriteRect.fTop - y); SkCopyConfig8888ToBitmap(dstBmp, srcPixels, bitmap.rowBytes(), config8888); if (drawSprite) { // we've clipped the sprite when we made a copy x = spriteRect.fLeft; y = spriteRect.fTop; sprite = &dstBmp; } else { return; } } SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode); SkRasterClip clip(SkIRect::MakeWH(fBitmap.width(), fBitmap.height())); SkDraw draw; draw.fRC = &clip; draw.fClip = &clip.bwRgn(); draw.fBitmap = &fBitmap; // canvas should have already called accessBitmap draw.fMatrix = &SkMatrix::I(); this->drawSprite(draw, *sprite, x, y, paint); }
void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder, GrDrawContext* drawContext, const GrClip& clip, GrDrawBatch* batch) { // Setup clip GrAppliedClip appliedClip; SkRect bounds; batch_bounds(&bounds, batch); if (!clip.apply(fContext, drawContext, &bounds, pipelineBuilder.isHWAntialias(), pipelineBuilder.hasUserStencilSettings(), &appliedClip)) { return; } // TODO: this is the only remaining usage of the AutoRestoreFragmentProcessorState - remove it GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps; if (appliedClip.getClipCoverageFragmentProcessor()) { arfps.set(&pipelineBuilder); arfps.addCoverageFragmentProcessor(sk_ref_sp(appliedClip.getClipCoverageFragmentProcessor())); } GrPipeline::CreateArgs args; args.fPipelineBuilder = &pipelineBuilder; args.fDrawContext = drawContext; args.fCaps = this->caps(); args.fScissor = &appliedClip.scissorState(); args.fHasStencilClip = appliedClip.hasStencilClip(); if (pipelineBuilder.hasUserStencilSettings() || appliedClip.hasStencilClip()) { if (!fResourceProvider->attachStencilAttachment(drawContext->accessRenderTarget())) { SkDebugf("ERROR creating stencil attachment. Draw skipped.\n"); return; } } batch->getPipelineOptimizations(&args.fOpts); GrScissorState finalScissor; if (args.fOpts.fOverrides.fUsePLSDstRead || fClipBatchToBounds) { GrGLIRect viewport; viewport.fLeft = 0; viewport.fBottom = 0; viewport.fWidth = drawContext->width(); viewport.fHeight = drawContext->height(); SkIRect ibounds; ibounds.fLeft = SkTPin(SkScalarFloorToInt(batch->bounds().fLeft), viewport.fLeft, viewport.fWidth); ibounds.fTop = SkTPin(SkScalarFloorToInt(batch->bounds().fTop), viewport.fBottom, viewport.fHeight); ibounds.fRight = SkTPin(SkScalarCeilToInt(batch->bounds().fRight), viewport.fLeft, viewport.fWidth); ibounds.fBottom = SkTPin(SkScalarCeilToInt(batch->bounds().fBottom), viewport.fBottom, viewport.fHeight); if (appliedClip.scissorState().enabled()) { const SkIRect& scissorRect = appliedClip.scissorState().rect(); if (!ibounds.intersect(scissorRect)) { return; } } finalScissor.set(ibounds); args.fScissor = &finalScissor; } args.fOpts.fColorPOI.completeCalculations( sk_sp_address_as_pointer_address(pipelineBuilder.fColorFragmentProcessors.begin()), pipelineBuilder.numColorFragmentProcessors()); args.fOpts.fCoveragePOI.completeCalculations( sk_sp_address_as_pointer_address(pipelineBuilder.fCoverageFragmentProcessors.begin()), pipelineBuilder.numCoverageFragmentProcessors()); if (!this->setupDstReadIfNecessary(pipelineBuilder, drawContext->accessRenderTarget(), clip, args.fOpts, &args.fDstTexture, batch->bounds())) { return; } if (!batch->installPipeline(args)) { return; } #ifdef ENABLE_MDB SkASSERT(fRenderTarget); batch->pipeline()->addDependenciesTo(fRenderTarget); #endif SkRect clippedBounds; SkAssertResult(intersect(&clippedBounds, bounds, appliedClip.deviceBounds())); this->recordBatch(batch, clippedBounds); }
// Draws the given bitmap to the given canvas. The subset of the source bitmap // identified by src_rect is drawn to the given destination rect. The bitmap // will be resampled to resample_width * resample_height (this is the size of // the whole image, not the subset). See shouldResampleBitmap for more. // // This does a lot of computation to resample only the portion of the bitmap // that will only be drawn. This is critical for performance since when we are // scrolling, for example, we are only drawing a small strip of the image. // Resampling the whole image every time is very slow, so this speeds up things // dramatically. static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) { // First get the subset we need. This is efficient and does not copy pixels. SkBitmap subset; bitmap.extractSubset(&subset, srcIRect); SkRect srcRect; srcRect.set(srcIRect); // Whether we're doing a subset or using the full source image. bool srcIsFull = srcIRect.fLeft == 0 && srcIRect.fTop == 0 && srcIRect.width() == bitmap.width() && srcIRect.height() == bitmap.height(); // We will always draw in integer sizes, so round the destination rect. SkIRect destRectRounded; destRect.round(&destRectRounded); SkIRect resizedImageRect = // Represents the size of the resized image. { 0, 0, destRectRounded.width(), destRectRounded.height() }; if (srcIsFull && bitmap.hasResizedBitmap(destRectRounded.width(), destRectRounded.height())) { // Yay, this bitmap frame already has a resized version. SkBitmap resampled = bitmap.resizedBitmap(destRectRounded.width(), destRectRounded.height()); canvas.drawBitmapRect(resampled, 0, destRect, &paint); return; } // Compute the visible portion of our rect. SkRect destBitmapSubsetSk; ClipRectToCanvas(canvas, destRect, &destBitmapSubsetSk); destBitmapSubsetSk.offset(-destRect.fLeft, -destRect.fTop); // The matrix inverting, etc. could have introduced rounding error which // causes the bounds to be outside of the resized bitmap. We round outward // so we always lean toward it being larger rather than smaller than we // need, and then clamp to the bitmap bounds so we don't get any invalid // data. SkIRect destBitmapSubsetSkI; destBitmapSubsetSk.roundOut(&destBitmapSubsetSkI); if (!destBitmapSubsetSkI.intersect(resizedImageRect)) return; // Resized image does not intersect. if (srcIsFull && bitmap.shouldCacheResampling( resizedImageRect.width(), resizedImageRect.height(), destBitmapSubsetSkI.width(), destBitmapSubsetSkI.height())) { // We're supposed to resize the entire image and cache it, even though // we don't need all of it. SkBitmap resampled = bitmap.resizedBitmap(destRectRounded.width(), destRectRounded.height()); canvas.drawBitmapRect(resampled, 0, destRect, &paint); } else { // We should only resize the exposed part of the bitmap to do the // minimal possible work. // Resample the needed part of the image. SkBitmap resampled = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destRectRounded.width(), destRectRounded.height(), destBitmapSubsetSkI); // Compute where the new bitmap should be drawn. Since our new bitmap // may be smaller than the original, we have to shift it over by the // same amount that we cut off the top and left. destBitmapSubsetSkI.offset(destRect.fLeft, destRect.fTop); SkRect offsetDestRect; offsetDestRect.set(destBitmapSubsetSkI); canvas.drawBitmapRect(resampled, 0, offsetDestRect, &paint); } }
sk_sp<SkSpecialImage> SkMergeImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { int inputCount = this->countInputs(); if (inputCount < 1) { return nullptr; } SkIRect bounds; bounds.setEmpty(); SkAutoTDeleteArray<sk_sp<SkSpecialImage>> inputs(new sk_sp<SkSpecialImage>[inputCount]); SkAutoTDeleteArray<SkIPoint> offsets(new SkIPoint[inputCount]); // Filter all of the inputs. for (int i = 0; i < inputCount; ++i) { offsets[i].setZero(); inputs[i] = this->filterInput(i, source, ctx, &offsets[i]); if (!inputs[i]) { continue; } const SkIRect inputBounds = SkIRect::MakeXYWH(offsets[i].fX, offsets[i].fY, inputs[i]->width(), inputs[i]->height()); bounds.join(inputBounds); } if (bounds.isEmpty()) { return nullptr; } // Apply the crop rect to the union of the inputs' bounds. // Note that the crop rect can only reduce the bounds, since this // filter does not affect transparent black. bool embiggen = false; this->getCropRect().applyTo(bounds, ctx.ctm(), embiggen, &bounds); if (!bounds.intersect(ctx.clipBounds())) { return nullptr; } const int x0 = bounds.left(); const int y0 = bounds.top(); sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), bounds.size())); if (!surf) { return nullptr; } SkCanvas* canvas = surf->getCanvas(); SkASSERT(canvas); canvas->clear(0x0); // Composite all of the filter inputs. for (int i = 0; i < inputCount; ++i) { if (!inputs[i]) { continue; } SkPaint paint; if (fModes) { paint.setBlendMode((SkBlendMode)fModes[i]); } inputs[i]->draw(canvas, SkIntToScalar(offsets[i].x() - x0), SkIntToScalar(offsets[i].y() - y0), &paint); } offset->fX = bounds.left(); offset->fY = bounds.top(); return surf->makeImageSnapshot(); }