blob: b69edab82cab030c7c541d86a9df1a7e3e44439e [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrSWMaskHelper.h"
#include "GrCaps.h"
#include "GrDrawTarget.h"
#include "GrGpu.h"
#include "GrPipelineBuilder.h"
#include "SkData.h"
#include "SkDistanceFieldGen.h"
#include "SkStrokeRec.h"
#include "batches/GrRectBatchFactory.h"
namespace {
/*
* Convert a boolean operation into a transfer mode code
*/
SkXfermode::Mode op_to_mode(SkRegion::Op op) {
static const SkXfermode::Mode modeMap[] = {
SkXfermode::kDstOut_Mode, // kDifference_Op
SkXfermode::kModulate_Mode, // kIntersect_Op
SkXfermode::kSrcOver_Mode, // kUnion_Op
SkXfermode::kXor_Mode, // kXOR_Op
SkXfermode::kClear_Mode, // kReverseDifference_Op
SkXfermode::kSrc_Mode, // kReplace_Op
};
return modeMap[op];
}
static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) {
GrPixelConfig config;
switch (fmt) {
case SkTextureCompressor::kLATC_Format:
config = kLATC_GrPixelConfig;
break;
case SkTextureCompressor::kR11_EAC_Format:
config = kR11_EAC_GrPixelConfig;
break;
case SkTextureCompressor::kASTC_12x12_Format:
config = kASTC_12x12_GrPixelConfig;
break;
case SkTextureCompressor::kETC1_Format:
config = kETC1_GrPixelConfig;
break;
default:
SkDEBUGFAIL("No GrPixelConfig for compression format!");
// Best guess
config = kAlpha_8_GrPixelConfig;
break;
}
return config;
}
static bool choose_compressed_fmt(const GrCaps* caps,
SkTextureCompressor::Format *fmt) {
if (nullptr == fmt) {
return false;
}
// We can't use scratch textures without the ability to update
// compressed textures...
if (!(caps->compressedTexSubImageSupport())) {
return false;
}
// Figure out what our preferred texture type is. If ASTC is available, that always
// gives the biggest win. Otherwise, in terms of compression speed and accuracy,
// LATC has a slight edge over R11 EAC.
if (caps->isConfigTexturable(kASTC_12x12_GrPixelConfig)) {
*fmt = SkTextureCompressor::kASTC_12x12_Format;
return true;
} else if (caps->isConfigTexturable(kLATC_GrPixelConfig)) {
*fmt = SkTextureCompressor::kLATC_Format;
return true;
} else if (caps->isConfigTexturable(kR11_EAC_GrPixelConfig)) {
*fmt = SkTextureCompressor::kR11_EAC_Format;
return true;
}
return false;
}
}
/**
* Draw a single rect element of the clip stack into the accumulation bitmap
*/
void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op,
bool antiAlias, uint8_t alpha) {
SkPaint paint;
SkASSERT(kNone_CompressionMode == fCompressionMode);
paint.setXfermode(SkXfermode::Make(op_to_mode(op)));
paint.setAntiAlias(antiAlias);
paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
fDraw.drawRect(rect, paint);
}
/**
* Draw a single path element of the clip stack into the accumulation bitmap
*/
void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
bool antiAlias, uint8_t alpha) {
SkPaint paint;
if (stroke.isHairlineStyle()) {
paint.setStyle(SkPaint::kStroke_Style);
} else {
if (stroke.isFillStyle()) {
paint.setStyle(SkPaint::kFill_Style);
} else {
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeJoin(stroke.getJoin());
paint.setStrokeCap(stroke.getCap());
paint.setStrokeWidth(stroke.getWidth());
}
}
paint.setAntiAlias(antiAlias);
SkTBlitterAllocator allocator;
SkBlitter* blitter = nullptr;
if (kBlitter_CompressionMode == fCompressionMode) {
SkASSERT(fCompressedBuffer.get());
blitter = SkTextureCompressor::CreateBlitterForFormat(
fPixels.width(), fPixels.height(), fCompressedBuffer.get(), &allocator,
fCompressedFormat);
}
if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
SkASSERT(0xFF == paint.getAlpha());
fDraw.drawPathCoverage(path, paint, blitter);
} else {
paint.setXfermodeMode(op_to_mode(op));
paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
fDraw.drawPath(path, paint, blitter);
}
}
bool GrSWMaskHelper::init(const SkIRect& resultBounds,
const SkMatrix* matrix,
bool allowCompression) {
if (matrix) {
fMatrix = *matrix;
} else {
fMatrix.setIdentity();
}
// Now translate so the bound's UL corner is at the origin
fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1,
-resultBounds.fTop * SK_Scalar1);
SkIRect bounds = SkIRect::MakeWH(resultBounds.width(),
resultBounds.height());
if (allowCompression &&
fContext->caps()->drawPathMasksToCompressedTexturesSupport() &&
choose_compressed_fmt(fContext->caps(), &fCompressedFormat)) {
fCompressionMode = kCompress_CompressionMode;
}
// Make sure that the width is a multiple of the desired block dimensions
// to allow for specialized SIMD instructions that compress multiple blocks at a time.
int cmpWidth = bounds.fRight;
int cmpHeight = bounds.fBottom;
if (kCompress_CompressionMode == fCompressionMode) {
int dimX, dimY;
SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX);
cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY);
// Can we create a blitter?
if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) {
int cmpSz = SkTextureCompressor::GetCompressedDataSize(
fCompressedFormat, cmpWidth, cmpHeight);
SkASSERT(cmpSz > 0);
SkASSERT(nullptr == fCompressedBuffer.get());
fCompressedBuffer.reset(cmpSz);
fCompressionMode = kBlitter_CompressionMode;
}
}
sk_bzero(&fDraw, sizeof(fDraw));
// If we don't have a custom blitter, then we either need a bitmap to compress
// from or a bitmap that we're going to use as a texture. In any case, we should
// allocate the pixels for a bitmap
const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight);
if (kBlitter_CompressionMode != fCompressionMode) {
if (!fPixels.tryAlloc(bmImageInfo)) {
return false;
}
fPixels.erase(0);
} else {
// Otherwise, we just need to remember how big the buffer is...
fPixels.reset(bmImageInfo);
}
fDraw.fDst = fPixels;
fRasterClip.setRect(bounds);
fDraw.fRC = &fRasterClip;
fDraw.fMatrix = &fMatrix;
return true;
}
/**
* Get a texture (from the texture cache) of the correct size & format.
*/
GrTexture* GrSWMaskHelper::createTexture() {
GrSurfaceDesc desc;
desc.fWidth = fPixels.width();
desc.fHeight = fPixels.height();
desc.fConfig = kAlpha_8_GrPixelConfig;
if (kNone_CompressionMode != fCompressionMode) {
#ifdef SK_DEBUG
int dimX, dimY;
SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
SkASSERT((desc.fWidth % dimX) == 0);
SkASSERT((desc.fHeight % dimY) == 0);
#endif
desc.fConfig = fmt_to_config(fCompressedFormat);
SkASSERT(fContext->caps()->isConfigTexturable(desc.fConfig));
}
return fContext->textureProvider()->createApproxTexture(desc);
}
void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrSurfaceDesc& desc,
const void *data, size_t rowbytes) {
// Since we're uploading to it, and it's compressed, 'texture' shouldn't
// have a render target.
SkASSERT(nullptr == texture->asRenderTarget());
texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, data, rowbytes);
}
void GrSWMaskHelper::compressTextureData(GrTexture *texture, const GrSurfaceDesc& desc) {
SkASSERT(GrPixelConfigIsCompressed(desc.fConfig));
SkASSERT(fmt_to_config(fCompressedFormat) == desc.fConfig);
SkAutoDataUnref cmpData(SkTextureCompressor::CompressBitmapToFormat(fPixels,
fCompressedFormat));
SkASSERT(cmpData);
this->sendTextureData(texture, desc, cmpData->data(), 0);
}
/**
* Move the result of the software mask generation back to the gpu
*/
void GrSWMaskHelper::toTexture(GrTexture *texture) {
GrSurfaceDesc desc;
desc.fWidth = fPixels.width();
desc.fHeight = fPixels.height();
desc.fConfig = texture->config();
// First see if we should compress this texture before uploading.
switch (fCompressionMode) {
case kNone_CompressionMode:
this->sendTextureData(texture, desc, fPixels.addr(), fPixels.rowBytes());
break;
case kCompress_CompressionMode:
this->compressTextureData(texture, desc);
break;
case kBlitter_CompressionMode:
SkASSERT(fCompressedBuffer.get());
this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0);
break;
}
}
/**
* Convert mask generation results to a signed distance field
*/
void GrSWMaskHelper::toSDF(unsigned char* sdf) {
SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fPixels.addr(),
fPixels.width(), fPixels.height(), fPixels.rowBytes());
}
////////////////////////////////////////////////////////////////////////////////
/**
* Software rasterizes path to A8 mask (possibly using the context's matrix)
* and uploads the result to a scratch texture. Returns the resulting
* texture on success; nullptr on failure.
*/
GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
const SkPath& path,
const SkStrokeRec& stroke,
const SkIRect& resultBounds,
bool antiAlias,
const SkMatrix* matrix) {
GrSWMaskHelper helper(context);
if (!helper.init(resultBounds, matrix)) {
return nullptr;
}
helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
GrTexture* texture(helper.createTexture());
if (!texture) {
return nullptr;
}
helper.toTexture(texture);
return texture;
}
void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
GrDrawTarget* target,
GrPipelineBuilder* pipelineBuilder,
GrColor color,
const SkMatrix& viewMatrix,
const SkIRect& rect) {
SkMatrix invert;
if (!viewMatrix.invert(&invert)) {
return;
}
GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps(*pipelineBuilder);
SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft,
SK_Scalar1 * rect.fTop,
SK_Scalar1 * rect.fRight,
SK_Scalar1 * rect.fBottom);
// We use device coords to compute the texture coordinates. We take the device coords and apply
// a translation so that the top-left of the device bounds maps to 0,0, and then a scaling
// matrix to normalized coords.
SkMatrix maskMatrix;
maskMatrix.setIDiv(texture->width(), texture->height());
maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
pipelineBuilder->addCoverageFragmentProcessor(
GrSimpleTextureEffect::Create(texture,
maskMatrix,
GrTextureParams::kNone_FilterMode,
kDevice_GrCoordSet))->unref();
SkAutoTUnref<GrDrawBatch> batch(GrRectBatchFactory::CreateNonAAFill(color, SkMatrix::I(),
dstRect, nullptr, &invert));
target->drawBatch(*pipelineBuilder, batch);
}