blob: 69dc0b5319797e70b7333c79170ccd22fed0093d [file] [log] [blame]
robertphillips@google.com58b20212012-06-27 20:44:52 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrSWMaskHelper.h"
krajcevski5c2fca02014-06-10 17:25:28 -07009
robertphillips@google.com58b20212012-06-27 20:44:52 +000010#include "GrDrawState.h"
robertphillips@google.com9528bdb2013-09-18 22:33:57 +000011#include "GrDrawTargetCaps.h"
robertphillips@google.com58b20212012-06-27 20:44:52 +000012#include "GrGpu.h"
13
krajcevski5c2fca02014-06-10 17:25:28 -070014#include "SkData.h"
sugoi@google.com5f74cf82012-12-17 21:16:45 +000015#include "SkStrokeRec.h"
sugoi@google.com12b4e272012-12-06 20:13:11 +000016
robertphillips@google.com58b20212012-06-27 20:44:52 +000017// TODO: try to remove this #include
18#include "GrContext.h"
19
20namespace {
krajcevski5c2fca02014-06-10 17:25:28 -070021
robertphillips@google.com58b20212012-06-27 20:44:52 +000022/*
23 * Convert a boolean operation into a transfer mode code
24 */
25SkXfermode::Mode op_to_mode(SkRegion::Op op) {
26
27 static const SkXfermode::Mode modeMap[] = {
28 SkXfermode::kDstOut_Mode, // kDifference_Op
reed@google.com8d3cd7a2013-01-30 21:36:11 +000029 SkXfermode::kModulate_Mode, // kIntersect_Op
robertphillips@google.com58b20212012-06-27 20:44:52 +000030 SkXfermode::kSrcOver_Mode, // kUnion_Op
31 SkXfermode::kXor_Mode, // kXOR_Op
32 SkXfermode::kClear_Mode, // kReverseDifference_Op
33 SkXfermode::kSrc_Mode, // kReplace_Op
34 };
35
36 return modeMap[op];
37}
38
krajcevski25a67bc2014-07-29 11:44:26 -070039static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) {
krajcevski25a67bc2014-07-29 11:44:26 -070040
krajcevski95b1b3d2014-08-07 12:58:38 -070041 GrPixelConfig config;
42 switch (fmt) {
43 case SkTextureCompressor::kLATC_Format:
44 config = kLATC_GrPixelConfig;
45 break;
46
47 case SkTextureCompressor::kR11_EAC_Format:
48 config = kR11_EAC_GrPixelConfig;
49 break;
50
51 case SkTextureCompressor::kASTC_12x12_Format:
52 config = kASTC_12x12_GrPixelConfig;
53 break;
54
55 case SkTextureCompressor::kETC1_Format:
56 config = kETC1_GrPixelConfig;
57 break;
58
59 default:
60 SkDEBUGFAIL("No GrPixelConfig for compression format!");
61 // Best guess
62 config = kAlpha_8_GrPixelConfig;
63 break;
64 }
65
66 return config;
krajcevski25a67bc2014-07-29 11:44:26 -070067}
68
krajcevskib3abe902014-07-30 13:08:11 -070069#if GR_COMPRESS_ALPHA_MASK
70static bool choose_compressed_fmt(const GrDrawTargetCaps* caps,
71 SkTextureCompressor::Format *fmt) {
72 if (NULL == fmt) {
73 return false;
74 }
75
76 // We can't use scratch textures without the ability to update
77 // compressed textures...
78 if (!(caps->compressedTexSubImageSupport())) {
79 return false;
80 }
81
82 // Figure out what our preferred texture type is. If ASTC is available, that always
83 // gives the biggest win. Otherwise, in terms of compression speed and accuracy,
84 // LATC has a slight edge over R11 EAC.
85 if (caps->isConfigTexturable(kASTC_12x12_GrPixelConfig)) {
86 *fmt = SkTextureCompressor::kASTC_12x12_Format;
87 return true;
88 } else if (caps->isConfigTexturable(kLATC_GrPixelConfig)) {
89 *fmt = SkTextureCompressor::kLATC_Format;
90 return true;
91 } else if (caps->isConfigTexturable(kR11_EAC_GrPixelConfig)) {
92 *fmt = SkTextureCompressor::kR11_EAC_Format;
93 return true;
94 }
95
96 return false;
97}
98#endif
99
robertphillips@google.com58b20212012-06-27 20:44:52 +0000100}
101
102/**
103 * Draw a single rect element of the clip stack into the accumulation bitmap
104 */
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000105void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op,
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000106 bool antiAlias, uint8_t alpha) {
robertphillips@google.com58b20212012-06-27 20:44:52 +0000107 SkPaint paint;
108
109 SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
110
111 paint.setXfermode(mode);
112 paint.setAntiAlias(antiAlias);
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000113 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
robertphillips@google.com58b20212012-06-27 20:44:52 +0000114
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000115 fDraw.drawRect(rect, paint);
robertphillips@google.com58b20212012-06-27 20:44:52 +0000116
117 SkSafeUnref(mode);
118}
119
120/**
121 * Draw a single path element of the clip stack into the accumulation bitmap
122 */
sugoi@google.com5f74cf82012-12-17 21:16:45 +0000123void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
sugoi@google.com12b4e272012-12-06 20:13:11 +0000124 bool antiAlias, uint8_t alpha) {
robertphillips@google.com58b20212012-06-27 20:44:52 +0000125
126 SkPaint paint;
sugoi@google.com5f74cf82012-12-17 21:16:45 +0000127 if (stroke.isHairlineStyle()) {
robertphillips@google.com58b20212012-06-27 20:44:52 +0000128 paint.setStyle(SkPaint::kStroke_Style);
129 paint.setStrokeWidth(SK_Scalar1);
130 } else {
sugoi@google.com5f74cf82012-12-17 21:16:45 +0000131 if (stroke.isFillStyle()) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000132 paint.setStyle(SkPaint::kFill_Style);
133 } else {
134 paint.setStyle(SkPaint::kStroke_Style);
135 paint.setStrokeJoin(stroke.getJoin());
136 paint.setStrokeCap(stroke.getCap());
sugoi@google.com5f74cf82012-12-17 21:16:45 +0000137 paint.setStrokeWidth(stroke.getWidth());
robertphillips@google.com58b20212012-06-27 20:44:52 +0000138 }
139 }
reed@google.com84e922b2013-11-04 20:57:36 +0000140 paint.setAntiAlias(antiAlias);
reed@google.com84e922b2013-11-04 20:57:36 +0000141
krajcevskib8ccc2f2014-08-07 08:15:14 -0700142 SkTBlitterAllocator allocator;
143 SkBlitter* blitter = NULL;
144 if (kBlitter_CompressionMode == fCompressionMode) {
145 SkASSERT(NULL != fCompressedBuffer.get());
146 blitter = SkTextureCompressor::CreateBlitterForFormat(
147 fBM.width(), fBM.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat);
148 }
149
reed@google.com126f7f52013-11-07 16:06:53 +0000150 if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
151 SkASSERT(0xFF == paint.getAlpha());
krajcevskib8ccc2f2014-08-07 08:15:14 -0700152 fDraw.drawPathCoverage(path, paint, blitter);
reed@google.com126f7f52013-11-07 16:06:53 +0000153 } else {
154 paint.setXfermodeMode(op_to_mode(op));
155 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
krajcevskib8ccc2f2014-08-07 08:15:14 -0700156 fDraw.drawPath(path, paint, blitter);
reed@google.com126f7f52013-11-07 16:06:53 +0000157 }
robertphillips@google.com58b20212012-06-27 20:44:52 +0000158}
159
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000160bool GrSWMaskHelper::init(const SkIRect& resultBounds,
reed@google.com84e922b2013-11-04 20:57:36 +0000161 const SkMatrix* matrix) {
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000162 if (NULL != matrix) {
163 fMatrix = *matrix;
robertphillips@google.com58b20212012-06-27 20:44:52 +0000164 } else {
165 fMatrix.setIdentity();
166 }
167
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000168 // Now translate so the bound's UL corner is at the origin
169 fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1,
170 -resultBounds.fTop * SK_Scalar1);
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000171 SkIRect bounds = SkIRect::MakeWH(resultBounds.width(),
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000172 resultBounds.height());
krajcevski25a67bc2014-07-29 11:44:26 -0700173
krajcevskib577c552014-07-16 13:26:43 -0700174#if GR_COMPRESS_ALPHA_MASK
krajcevskib8ccc2f2014-08-07 08:15:14 -0700175 if (choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat)) {
176 fCompressionMode = kCompress_CompressionMode;
177 }
krajcevskib3abe902014-07-30 13:08:11 -0700178#endif
krajcevski25a67bc2014-07-29 11:44:26 -0700179
krajcevskib8ccc2f2014-08-07 08:15:14 -0700180 // Make sure that the width is a multiple of the desired block dimensions
181 // to allow for specialized SIMD instructions that compress multiple blocks at a time.
182 int cmpWidth = bounds.fRight;
183 int cmpHeight = bounds.fBottom;
184 if (kCompress_CompressionMode == fCompressionMode) {
krajcevskib3abe902014-07-30 13:08:11 -0700185 int dimX, dimY;
186 SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
krajcevskib8ccc2f2014-08-07 08:15:14 -0700187 cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX);
188 cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY);
189
190 // Can we create a blitter?
191 if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) {
192 int cmpSz = SkTextureCompressor::GetCompressedDataSize(
193 fCompressedFormat, cmpWidth, cmpHeight);
194
195 SkASSERT(cmpSz > 0);
196 SkASSERT(NULL == fCompressedBuffer.get());
197 fCompressedBuffer.reset(cmpSz);
198 fCompressionMode = kBlitter_CompressionMode;
199 }
200 }
201
202 // If we don't have a custom blitter, then we either need a bitmap to compress
203 // from or a bitmap that we're going to use as a texture. In any case, we should
204 // allocate the pixels for a bitmap
205 const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight);
206 if (kBlitter_CompressionMode != fCompressionMode) {
207 if (!fBM.allocPixels(bmImageInfo)) {
208 return false;
209 }
210
211 sk_bzero(fBM.getPixels(), fBM.getSafeSize());
krajcevskib3abe902014-07-30 13:08:11 -0700212 } else {
krajcevskib8ccc2f2014-08-07 08:15:14 -0700213 // Otherwise, we just need to remember how big the buffer is...
214 fBM.setInfo(bmImageInfo);
krajcevskib3abe902014-07-30 13:08:11 -0700215 }
robertphillips@google.com58b20212012-06-27 20:44:52 +0000216
robertphillips@google.com58b20212012-06-27 20:44:52 +0000217 sk_bzero(&fDraw, sizeof(fDraw));
krajcevskib8ccc2f2014-08-07 08:15:14 -0700218
robertphillips@google.com58b20212012-06-27 20:44:52 +0000219 fRasterClip.setRect(bounds);
220 fDraw.fRC = &fRasterClip;
221 fDraw.fClip = &fRasterClip.bwRgn();
222 fDraw.fMatrix = &fMatrix;
223 fDraw.fBitmap = &fBM;
224 return true;
225}
226
227/**
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000228 * Get a texture (from the texture cache) of the correct size & format.
229 * Return true on success; false on failure.
robertphillips@google.com58b20212012-06-27 20:44:52 +0000230 */
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000231bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* texture) {
robertphillips@google.com58b20212012-06-27 20:44:52 +0000232 GrTextureDesc desc;
233 desc.fWidth = fBM.width();
234 desc.fHeight = fBM.height();
krajcevskib3abe902014-07-30 13:08:11 -0700235 desc.fConfig = kAlpha_8_GrPixelConfig;
krajcevskifb4f5cb2014-06-12 09:20:38 -0700236
krajcevskib8ccc2f2014-08-07 08:15:14 -0700237 if (kNone_CompressionMode != fCompressionMode) {
krajcevskib577c552014-07-16 13:26:43 -0700238
krajcevski25a67bc2014-07-29 11:44:26 -0700239#ifdef SK_DEBUG
krajcevskib3abe902014-07-30 13:08:11 -0700240 int dimX, dimY;
241 SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
242 SkASSERT((desc.fWidth % dimX) == 0);
243 SkASSERT((desc.fHeight % dimY) == 0);
krajcevski25a67bc2014-07-29 11:44:26 -0700244#endif
245
krajcevskib3abe902014-07-30 13:08:11 -0700246 desc.fConfig = fmt_to_config(fCompressedFormat);
krajcevskib8ccc2f2014-08-07 08:15:14 -0700247 SkASSERT(fContext->getGpu()->caps()->isConfigTexturable(desc.fConfig));
krajcevskifb4f5cb2014-06-12 09:20:38 -0700248 }
robertphillips@google.com58b20212012-06-27 20:44:52 +0000249
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000250 texture->set(fContext, desc);
251 return NULL != texture->texture();
robertphillips@google.com58b20212012-06-27 20:44:52 +0000252}
253
krajcevskifb4f5cb2014-06-12 09:20:38 -0700254void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrTextureDesc& desc,
255 const void *data, int rowbytes) {
256 // If we aren't reusing scratch textures we don't need to flush before
257 // writing since no one else will be using 'texture'
258 bool reuseScratch = fContext->getGpu()->caps()->reuseScratchTextures();
krajcevski5c2fca02014-06-10 17:25:28 -0700259
krajcevskifb4f5cb2014-06-12 09:20:38 -0700260 // Since we're uploading to it, and it's compressed, 'texture' shouldn't
261 // have a render target.
262 SkASSERT(NULL == texture->asRenderTarget());
263
264 texture->writePixels(0, 0, desc.fWidth, desc.fHeight,
265 desc.fConfig, data, rowbytes,
266 reuseScratch ? 0 : GrContext::kDontFlush_PixelOpsFlag);
krajcevski5c2fca02014-06-10 17:25:28 -0700267}
268
krajcevskib577c552014-07-16 13:26:43 -0700269void GrSWMaskHelper::compressTextureData(GrTexture *texture, const GrTextureDesc& desc) {
270
271 SkASSERT(GrPixelConfigIsCompressed(desc.fConfig));
krajcevskib3abe902014-07-30 13:08:11 -0700272 SkASSERT(fmt_to_config(fCompressedFormat) == desc.fConfig);
krajcevskib577c552014-07-16 13:26:43 -0700273
krajcevskib3abe902014-07-30 13:08:11 -0700274 SkAutoDataUnref cmpData(SkTextureCompressor::CompressBitmapToFormat(fBM, fCompressedFormat));
krajcevskib577c552014-07-16 13:26:43 -0700275 SkASSERT(NULL != cmpData);
276
277 this->sendTextureData(texture, desc, cmpData->data(), 0);
278}
279
robertphillips@google.com58b20212012-06-27 20:44:52 +0000280/**
281 * Move the result of the software mask generation back to the gpu
282 */
robertphillips@google.comd92cf2e2013-07-19 18:13:02 +0000283void GrSWMaskHelper::toTexture(GrTexture *texture) {
robertphillips@google.com58b20212012-06-27 20:44:52 +0000284 SkAutoLockPixels alp(fBM);
285
krajcevskifb4f5cb2014-06-12 09:20:38 -0700286 GrTextureDesc desc;
287 desc.fWidth = fBM.width();
288 desc.fHeight = fBM.height();
289 desc.fConfig = texture->config();
290
291 // First see if we should compress this texture before uploading.
krajcevskib8ccc2f2014-08-07 08:15:14 -0700292 switch (fCompressionMode) {
293 case kNone_CompressionMode:
294 this->sendTextureData(texture, desc, fBM.getPixels(), fBM.rowBytes());
295 break;
296
297 case kCompress_CompressionMode:
298 this->compressTextureData(texture, desc);
299 break;
300
301 case kBlitter_CompressionMode:
302 SkASSERT(NULL != fCompressedBuffer.get());
303 this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0);
304 break;
krajcevskifb4f5cb2014-06-12 09:20:38 -0700305 }
robertphillips@google.com58b20212012-06-27 20:44:52 +0000306}
307
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000308////////////////////////////////////////////////////////////////////////////////
309/**
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000310 * Software rasterizes path to A8 mask (possibly using the context's matrix)
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000311 * and uploads the result to a scratch texture. Returns the resulting
312 * texture on success; NULL on failure.
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000313 */
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000314GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
315 const SkPath& path,
sugoi@google.com5f74cf82012-12-17 21:16:45 +0000316 const SkStrokeRec& stroke,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000317 const SkIRect& resultBounds,
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000318 bool antiAlias,
bsalomon@google.comb9086a02012-11-01 18:02:54 +0000319 SkMatrix* matrix) {
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000320 GrSWMaskHelper helper(context);
321
reed@google.com84e922b2013-11-04 20:57:36 +0000322 if (!helper.init(resultBounds, matrix)) {
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000323 return NULL;
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000324 }
325
sugoi@google.com12b4e272012-12-06 20:13:11 +0000326 helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000327
krajcevski5c2fca02014-06-10 17:25:28 -0700328 GrAutoScratchTexture ast;
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000329 if (!helper.getTexture(&ast)) {
330 return NULL;
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000331 }
332
robertphillips@google.comd92cf2e2013-07-19 18:13:02 +0000333 helper.toTexture(ast.texture());
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000334
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000335 return ast.detach();
336}
337
338void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
339 GrDrawTarget* target,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000340 const SkIRect& rect) {
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000341 GrDrawState* drawState = target->drawState();
342
bsalomon@google.com137f1342013-05-29 21:27:53 +0000343 GrDrawState::AutoViewMatrixRestore avmr;
344 if (!avmr.setIdentity(drawState)) {
bsalomon@google.come3d32162012-07-20 13:37:06 +0000345 return;
346 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000347 GrDrawState::AutoRestoreEffects are(drawState);
robertphillips@google.com5dfb6722012-07-09 16:32:28 +0000348
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000349 SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft,
350 SK_Scalar1 * rect.fTop,
351 SK_Scalar1 * rect.fRight,
352 SK_Scalar1 * rect.fBottom);
bsalomon@google.comc7818882013-03-20 19:19:53 +0000353
354 // We want to use device coords to compute the texture coordinates. We set our matrix to be
355 // equal to the view matrix followed by a translation so that the top-left of the device bounds
356 // maps to 0,0, and then a scaling matrix to normalized coords. We apply this matrix to the
357 // vertex positions rather than local coords.
358 SkMatrix maskMatrix;
359 maskMatrix.setIDiv(texture->width(), texture->height());
360 maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
361 maskMatrix.preConcat(drawState->getViewMatrix());
362
bsalomon@google.comeb6879f2013-06-13 19:34:18 +0000363 drawState->addCoverageEffect(
bsalomon@google.comc7818882013-03-20 19:19:53 +0000364 GrSimpleTextureEffect::Create(texture,
365 maskMatrix,
humper@google.comb86add12013-07-25 18:49:07 +0000366 GrTextureParams::kNone_FilterMode,
bsalomon@google.com77af6802013-10-02 13:04:56 +0000367 kPosition_GrCoordSet))->unref();
bsalomon@google.comc7818882013-03-20 19:19:53 +0000368
369 target->drawSimpleRect(dstRect);
robertphillips@google.com366f1c62012-06-29 21:38:47 +0000370}