1/*
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"
9
10#include "GrDrawState.h"
11#include "GrDrawTargetCaps.h"
12#include "GrGpu.h"
13
14#include "SkData.h"
15#include "SkStrokeRec.h"
16
17// TODO: try to remove this #include
18#include "GrContext.h"
19
20namespace {
21
22/*
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
29        SkXfermode::kModulate_Mode, // kIntersect_Op
30        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
39static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) {
40
41    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;
67}
68
69static bool choose_compressed_fmt(const GrDrawTargetCaps* caps,
70                                  SkTextureCompressor::Format *fmt) {
71    if (NULL == fmt) {
72        return false;
73    }
74
75    // We can't use scratch textures without the ability to update
76    // compressed textures...
77    if (!(caps->compressedTexSubImageSupport())) {
78        return false;
79    }
80
81    // Figure out what our preferred texture type is. If ASTC is available, that always
82    // gives the biggest win. Otherwise, in terms of compression speed and accuracy,
83    // LATC has a slight edge over R11 EAC.
84    if (caps->isConfigTexturable(kASTC_12x12_GrPixelConfig)) {
85        *fmt = SkTextureCompressor::kASTC_12x12_Format;
86        return true;
87    } else if (caps->isConfigTexturable(kLATC_GrPixelConfig)) {
88        *fmt = SkTextureCompressor::kLATC_Format;
89        return true;
90    } else if (caps->isConfigTexturable(kR11_EAC_GrPixelConfig)) {
91        *fmt = SkTextureCompressor::kR11_EAC_Format;
92        return true;
93    }
94
95    return false;
96}
97
98}
99
100/**
101 * Draw a single rect element of the clip stack into the accumulation bitmap
102 */
103void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op,
104                          bool antiAlias, uint8_t alpha) {
105    SkPaint paint;
106
107    SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
108
109    SkASSERT(kNone_CompressionMode == fCompressionMode);
110
111    paint.setXfermode(mode);
112    paint.setAntiAlias(antiAlias);
113    paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
114
115    fDraw.drawRect(rect, paint);
116
117    SkSafeUnref(mode);
118}
119
120/**
121 * Draw a single path element of the clip stack into the accumulation bitmap
122 */
123void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
124                          bool antiAlias, uint8_t alpha) {
125
126    SkPaint paint;
127    if (stroke.isHairlineStyle()) {
128        paint.setStyle(SkPaint::kStroke_Style);
129        paint.setStrokeWidth(SK_Scalar1);
130    } else {
131        if (stroke.isFillStyle()) {
132            paint.setStyle(SkPaint::kFill_Style);
133        } else {
134            paint.setStyle(SkPaint::kStroke_Style);
135            paint.setStrokeJoin(stroke.getJoin());
136            paint.setStrokeCap(stroke.getCap());
137            paint.setStrokeWidth(stroke.getWidth());
138        }
139    }
140    paint.setAntiAlias(antiAlias);
141
142    SkTBlitterAllocator allocator;
143    SkBlitter* blitter = NULL;
144    if (kBlitter_CompressionMode == fCompressionMode) {
145        SkASSERT(fCompressedBuffer.get());
146        blitter = SkTextureCompressor::CreateBlitterForFormat(
147            fBM.width(), fBM.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat);
148    }
149
150    if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
151        SkASSERT(0xFF == paint.getAlpha());
152        fDraw.drawPathCoverage(path, paint, blitter);
153    } else {
154        paint.setXfermodeMode(op_to_mode(op));
155        paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
156        fDraw.drawPath(path, paint, blitter);
157    }
158}
159
160bool GrSWMaskHelper::init(const SkIRect& resultBounds,
161                          const SkMatrix* matrix,
162                          bool allowCompression) {
163    if (matrix) {
164        fMatrix = *matrix;
165    } else {
166        fMatrix.setIdentity();
167    }
168
169    // Now translate so the bound's UL corner is at the origin
170    fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1,
171                          -resultBounds.fTop * SK_Scalar1);
172    SkIRect bounds = SkIRect::MakeWH(resultBounds.width(),
173                                     resultBounds.height());
174
175    if (allowCompression &&
176        fContext->getOptions().fDrawPathToCompressedTexture &&
177        choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat)) {
178        fCompressionMode = kCompress_CompressionMode;
179    }
180
181    // Make sure that the width is a multiple of the desired block dimensions
182    // to allow for specialized SIMD instructions that compress multiple blocks at a time.
183    int cmpWidth = bounds.fRight;
184    int cmpHeight = bounds.fBottom;
185    if (kCompress_CompressionMode == fCompressionMode) {
186        int dimX, dimY;
187        SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
188        cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX);
189        cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY);
190
191        // Can we create a blitter?
192        if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) {
193            int cmpSz = SkTextureCompressor::GetCompressedDataSize(
194                fCompressedFormat, cmpWidth, cmpHeight);
195
196            SkASSERT(cmpSz > 0);
197            SkASSERT(NULL == fCompressedBuffer.get());
198            fCompressedBuffer.reset(cmpSz);
199            fCompressionMode = kBlitter_CompressionMode;
200        }
201    }
202
203    // If we don't have a custom blitter, then we either need a bitmap to compress
204    // from or a bitmap that we're going to use as a texture. In any case, we should
205    // allocate the pixels for a bitmap
206    const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight);
207    if (kBlitter_CompressionMode != fCompressionMode) {
208        if (!fBM.tryAllocPixels(bmImageInfo)) {
209            return false;
210        }
211
212        sk_bzero(fBM.getPixels(), fBM.getSafeSize());
213    } else {
214        // Otherwise, we just need to remember how big the buffer is...
215        fBM.setInfo(bmImageInfo);
216    }
217
218    sk_bzero(&fDraw, sizeof(fDraw));
219
220    fRasterClip.setRect(bounds);
221    fDraw.fRC    = &fRasterClip;
222    fDraw.fClip  = &fRasterClip.bwRgn();
223    fDraw.fMatrix = &fMatrix;
224    fDraw.fBitmap = &fBM;
225    return true;
226}
227
228/**
229 * Get a texture (from the texture cache) of the correct size & format.
230 * Return true on success; false on failure.
231 */
232bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* texture) {
233    GrTextureDesc desc;
234    desc.fWidth = fBM.width();
235    desc.fHeight = fBM.height();
236    desc.fConfig = kAlpha_8_GrPixelConfig;
237
238    if (kNone_CompressionMode != fCompressionMode) {
239
240#ifdef SK_DEBUG
241        int dimX, dimY;
242        SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
243        SkASSERT((desc.fWidth % dimX) == 0);
244        SkASSERT((desc.fHeight % dimY) == 0);
245#endif
246
247        desc.fConfig = fmt_to_config(fCompressedFormat);
248        SkASSERT(fContext->getGpu()->caps()->isConfigTexturable(desc.fConfig));
249    }
250
251    texture->set(fContext, desc);
252    return SkToBool(texture->texture());
253}
254
255void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrTextureDesc& desc,
256                                     const void *data, int rowbytes) {
257    // If we aren't reusing scratch textures we don't need to flush before
258    // writing since no one else will be using 'texture'
259    bool reuseScratch = fContext->getGpu()->caps()->reuseScratchTextures();
260
261    // Since we're uploading to it, and it's compressed, 'texture' shouldn't
262    // have a render target.
263    SkASSERT(NULL == texture->asRenderTarget());
264
265    texture->writePixels(0, 0, desc.fWidth, desc.fHeight,
266                         desc.fConfig, data, rowbytes,
267                         reuseScratch ? 0 : GrContext::kDontFlush_PixelOpsFlag);
268}
269
270void GrSWMaskHelper::compressTextureData(GrTexture *texture, const GrTextureDesc& desc) {
271
272    SkASSERT(GrPixelConfigIsCompressed(desc.fConfig));
273    SkASSERT(fmt_to_config(fCompressedFormat) == desc.fConfig);
274
275    SkAutoDataUnref cmpData(SkTextureCompressor::CompressBitmapToFormat(fBM, fCompressedFormat));
276    SkASSERT(cmpData);
277
278    this->sendTextureData(texture, desc, cmpData->data(), 0);
279}
280
281/**
282 * Move the result of the software mask generation back to the gpu
283 */
284void GrSWMaskHelper::toTexture(GrTexture *texture) {
285    SkAutoLockPixels alp(fBM);
286
287    GrTextureDesc desc;
288    desc.fWidth = fBM.width();
289    desc.fHeight = fBM.height();
290    desc.fConfig = texture->config();
291
292    // First see if we should compress this texture before uploading.
293    switch (fCompressionMode) {
294        case kNone_CompressionMode:
295            this->sendTextureData(texture, desc, fBM.getPixels(), fBM.rowBytes());
296            break;
297
298        case kCompress_CompressionMode:
299            this->compressTextureData(texture, desc);
300            break;
301
302        case kBlitter_CompressionMode:
303            SkASSERT(fCompressedBuffer.get());
304            this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0);
305            break;
306    }
307}
308
309////////////////////////////////////////////////////////////////////////////////
310/**
311 * Software rasterizes path to A8 mask (possibly using the context's matrix)
312 * and uploads the result to a scratch texture. Returns the resulting
313 * texture on success; NULL on failure.
314 */
315GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
316                                                 const SkPath& path,
317                                                 const SkStrokeRec& stroke,
318                                                 const SkIRect& resultBounds,
319                                                 bool antiAlias,
320                                                 SkMatrix* matrix) {
321    GrSWMaskHelper helper(context);
322
323    if (!helper.init(resultBounds, matrix)) {
324        return NULL;
325    }
326
327    helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
328
329    GrAutoScratchTexture ast;
330    if (!helper.getTexture(&ast)) {
331        return NULL;
332    }
333
334    helper.toTexture(ast.texture());
335
336    return ast.detach();
337}
338
339void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
340                                              GrDrawTarget* target,
341                                              const SkIRect& rect) {
342    GrDrawState* drawState = target->drawState();
343
344    GrDrawState::AutoViewMatrixRestore avmr;
345    if (!avmr.setIdentity(drawState)) {
346        return;
347    }
348    GrDrawState::AutoRestoreEffects are(drawState);
349
350    SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft,
351                                      SK_Scalar1 * rect.fTop,
352                                      SK_Scalar1 * rect.fRight,
353                                      SK_Scalar1 * rect.fBottom);
354
355    // We want to use device coords to compute the texture coordinates. We set our matrix to be
356    // equal to the view matrix followed by a translation so that the top-left of the device bounds
357    // maps to 0,0, and then a scaling matrix to normalized coords. We apply this matrix to the
358    // vertex positions rather than local coords.
359    SkMatrix maskMatrix;
360    maskMatrix.setIDiv(texture->width(), texture->height());
361    maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
362    maskMatrix.preConcat(drawState->getViewMatrix());
363
364    drawState->addCoverageProcessor(
365                         GrSimpleTextureEffect::Create(texture,
366                                                       maskMatrix,
367                                                       GrTextureParams::kNone_FilterMode,
368                                                       kPosition_GrCoordSet))->unref();
369
370    target->drawSimpleRect(dstRect);
371}
372