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 "GrCaps.h"
11#include "GrDrawTarget.h"
12#include "GrGpu.h"
13#include "GrPipelineBuilder.h"
14
15#include "SkData.h"
16#include "SkDistanceFieldGen.h"
17#include "SkStrokeRec.h"
18
19#include "batches/GrRectBatchFactory.h"
20
21namespace {
22
23/*
24 * Convert a boolean operation into a transfer mode code
25 */
26SkXfermode::Mode op_to_mode(SkRegion::Op op) {
27
28    static const SkXfermode::Mode modeMap[] = {
29        SkXfermode::kDstOut_Mode,   // kDifference_Op
30        SkXfermode::kModulate_Mode, // kIntersect_Op
31        SkXfermode::kSrcOver_Mode,  // kUnion_Op
32        SkXfermode::kXor_Mode,      // kXOR_Op
33        SkXfermode::kClear_Mode,    // kReverseDifference_Op
34        SkXfermode::kSrc_Mode,      // kReplace_Op
35    };
36
37    return modeMap[op];
38}
39
40static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) {
41
42    GrPixelConfig config;
43    switch (fmt) {
44        case SkTextureCompressor::kLATC_Format:
45            config = kLATC_GrPixelConfig;
46            break;
47
48        case SkTextureCompressor::kR11_EAC_Format:
49            config = kR11_EAC_GrPixelConfig;
50            break;
51
52        case SkTextureCompressor::kASTC_12x12_Format:
53            config = kASTC_12x12_GrPixelConfig;
54            break;
55
56        case SkTextureCompressor::kETC1_Format:
57            config = kETC1_GrPixelConfig;
58            break;
59
60        default:
61            SkDEBUGFAIL("No GrPixelConfig for compression format!");
62            // Best guess
63            config = kAlpha_8_GrPixelConfig;
64            break;
65    }
66
67    return config;
68}
69
70static bool choose_compressed_fmt(const GrCaps* caps,
71                                  SkTextureCompressor::Format *fmt) {
72    if (nullptr == 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
99}
100
101/**
102 * Draw a single rect element of the clip stack into the accumulation bitmap
103 */
104void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op,
105                          bool antiAlias, uint8_t alpha) {
106    SkPaint paint;
107
108    SkXfermode* mode = SkXfermode::Create(op_to_mode(op));
109
110    SkASSERT(kNone_CompressionMode == fCompressionMode);
111
112    paint.setXfermode(mode);
113    paint.setAntiAlias(antiAlias);
114    paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
115
116    fDraw.drawRect(rect, paint);
117
118    SkSafeUnref(mode);
119}
120
121/**
122 * Draw a single path element of the clip stack into the accumulation bitmap
123 */
124void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op,
125                          bool antiAlias, uint8_t alpha) {
126
127    SkPaint paint;
128    if (stroke.isHairlineStyle()) {
129        paint.setStyle(SkPaint::kStroke_Style);
130        paint.setStrokeWidth(SK_Scalar1);
131    } else {
132        if (stroke.isFillStyle()) {
133            paint.setStyle(SkPaint::kFill_Style);
134        } else {
135            paint.setStyle(SkPaint::kStroke_Style);
136            paint.setStrokeJoin(stroke.getJoin());
137            paint.setStrokeCap(stroke.getCap());
138            paint.setStrokeWidth(stroke.getWidth());
139        }
140    }
141    paint.setAntiAlias(antiAlias);
142
143    SkTBlitterAllocator allocator;
144    SkBlitter* blitter = nullptr;
145    if (kBlitter_CompressionMode == fCompressionMode) {
146        SkASSERT(fCompressedBuffer.get());
147        blitter = SkTextureCompressor::CreateBlitterForFormat(
148            fPixels.width(), fPixels.height(), fCompressedBuffer.get(), &allocator,
149                                                              fCompressedFormat);
150    }
151
152    if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
153        SkASSERT(0xFF == paint.getAlpha());
154        fDraw.drawPathCoverage(path, paint, blitter);
155    } else {
156        paint.setXfermodeMode(op_to_mode(op));
157        paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
158        fDraw.drawPath(path, paint, blitter);
159    }
160}
161
162bool GrSWMaskHelper::init(const SkIRect& resultBounds,
163                          const SkMatrix* matrix,
164                          bool allowCompression) {
165    if (matrix) {
166        fMatrix = *matrix;
167    } else {
168        fMatrix.setIdentity();
169    }
170
171    // Now translate so the bound's UL corner is at the origin
172    fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1,
173                          -resultBounds.fTop * SK_Scalar1);
174    SkIRect bounds = SkIRect::MakeWH(resultBounds.width(),
175                                     resultBounds.height());
176
177    if (allowCompression &&
178        fContext->caps()->drawPathMasksToCompressedTexturesSupport() &&
179        choose_compressed_fmt(fContext->caps(), &fCompressedFormat)) {
180        fCompressionMode = kCompress_CompressionMode;
181    }
182
183    // Make sure that the width is a multiple of the desired block dimensions
184    // to allow for specialized SIMD instructions that compress multiple blocks at a time.
185    int cmpWidth = bounds.fRight;
186    int cmpHeight = bounds.fBottom;
187    if (kCompress_CompressionMode == fCompressionMode) {
188        int dimX, dimY;
189        SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
190        cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX);
191        cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY);
192
193        // Can we create a blitter?
194        if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) {
195            int cmpSz = SkTextureCompressor::GetCompressedDataSize(
196                fCompressedFormat, cmpWidth, cmpHeight);
197
198            SkASSERT(cmpSz > 0);
199            SkASSERT(nullptr == fCompressedBuffer.get());
200            fCompressedBuffer.reset(cmpSz);
201            fCompressionMode = kBlitter_CompressionMode;
202        }
203    }
204
205    sk_bzero(&fDraw, sizeof(fDraw));
206
207    // If we don't have a custom blitter, then we either need a bitmap to compress
208    // from or a bitmap that we're going to use as a texture. In any case, we should
209    // allocate the pixels for a bitmap
210    const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight);
211    if (kBlitter_CompressionMode != fCompressionMode) {
212        if (!fPixels.tryAlloc(bmImageInfo)) {
213            return false;
214        }
215        fPixels.erase(0);
216    } else {
217        // Otherwise, we just need to remember how big the buffer is...
218        fPixels.reset(bmImageInfo);
219    }
220    fDraw.fDst      = fPixels;
221    fRasterClip.setRect(bounds);
222    fDraw.fRC       = &fRasterClip;
223    fDraw.fClip     = &fRasterClip.bwRgn();
224    fDraw.fMatrix   = &fMatrix;
225    return true;
226}
227
228/**
229 * Get a texture (from the texture cache) of the correct size & format.
230 */
231GrTexture* GrSWMaskHelper::createTexture() {
232    GrSurfaceDesc desc;
233    desc.fWidth = fPixels.width();
234    desc.fHeight = fPixels.height();
235    desc.fConfig = kAlpha_8_GrPixelConfig;
236
237    if (kNone_CompressionMode != fCompressionMode) {
238
239#ifdef SK_DEBUG
240        int dimX, dimY;
241        SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
242        SkASSERT((desc.fWidth % dimX) == 0);
243        SkASSERT((desc.fHeight % dimY) == 0);
244#endif
245
246        desc.fConfig = fmt_to_config(fCompressedFormat);
247        SkASSERT(fContext->caps()->isConfigTexturable(desc.fConfig));
248    }
249
250    return fContext->textureProvider()->createApproxTexture(desc);
251}
252
253void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrSurfaceDesc& desc,
254                                     const void *data, size_t rowbytes) {
255    // Since we're uploading to it, and it's compressed, 'texture' shouldn't
256    // have a render target.
257    SkASSERT(nullptr == texture->asRenderTarget());
258
259    texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, data, rowbytes);
260}
261
262void GrSWMaskHelper::compressTextureData(GrTexture *texture, const GrSurfaceDesc& desc) {
263
264    SkASSERT(GrPixelConfigIsCompressed(desc.fConfig));
265    SkASSERT(fmt_to_config(fCompressedFormat) == desc.fConfig);
266
267    SkAutoDataUnref cmpData(SkTextureCompressor::CompressBitmapToFormat(fPixels,
268                                                                        fCompressedFormat));
269    SkASSERT(cmpData);
270
271    this->sendTextureData(texture, desc, cmpData->data(), 0);
272}
273
274/**
275 * Move the result of the software mask generation back to the gpu
276 */
277void GrSWMaskHelper::toTexture(GrTexture *texture) {
278    GrSurfaceDesc desc;
279    desc.fWidth = fPixels.width();
280    desc.fHeight = fPixels.height();
281    desc.fConfig = texture->config();
282
283    // First see if we should compress this texture before uploading.
284    switch (fCompressionMode) {
285        case kNone_CompressionMode:
286            this->sendTextureData(texture, desc, fPixels.addr(), fPixels.rowBytes());
287            break;
288
289        case kCompress_CompressionMode:
290            this->compressTextureData(texture, desc);
291            break;
292
293        case kBlitter_CompressionMode:
294            SkASSERT(fCompressedBuffer.get());
295            this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0);
296            break;
297    }
298}
299
300/**
301 * Convert mask generation results to a signed distance field
302 */
303void GrSWMaskHelper::toSDF(unsigned char* sdf) {
304    SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fPixels.addr(),
305                                       fPixels.width(), fPixels.height(), fPixels.rowBytes());
306}
307
308////////////////////////////////////////////////////////////////////////////////
309/**
310 * Software rasterizes path to A8 mask (possibly using the context's matrix)
311 * and uploads the result to a scratch texture. Returns the resulting
312 * texture on success; nullptr on failure.
313 */
314GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context,
315                                                 const SkPath& path,
316                                                 const SkStrokeRec& stroke,
317                                                 const SkIRect& resultBounds,
318                                                 bool antiAlias,
319                                                 const SkMatrix* matrix) {
320    GrSWMaskHelper helper(context);
321
322    if (!helper.init(resultBounds, matrix)) {
323        return nullptr;
324    }
325
326    helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
327
328    GrTexture* texture(helper.createTexture());
329    if (!texture) {
330        return nullptr;
331    }
332
333    helper.toTexture(texture);
334
335    return texture;
336}
337
338void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture,
339                                              GrDrawTarget* target,
340                                              GrPipelineBuilder* pipelineBuilder,
341                                              GrColor color,
342                                              const SkMatrix& viewMatrix,
343                                              const SkIRect& rect) {
344    SkMatrix invert;
345    if (!viewMatrix.invert(&invert)) {
346        return;
347    }
348    GrPipelineBuilder::AutoRestoreFragmentProcessorState arfps(*pipelineBuilder);
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 use device coords to compute the texture coordinates. We take the device coords and apply
356    // a translation so that the top-left of the device bounds maps to 0,0, and then a scaling
357    // matrix to normalized coords.
358    SkMatrix maskMatrix;
359    maskMatrix.setIDiv(texture->width(), texture->height());
360    maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
361
362    pipelineBuilder->addCoverageFragmentProcessor(
363                         GrSimpleTextureEffect::Create(texture,
364                                                       maskMatrix,
365                                                       GrTextureParams::kNone_FilterMode,
366                                                       kDevice_GrCoordSet))->unref();
367
368    SkAutoTUnref<GrDrawBatch> batch(GrRectBatchFactory::CreateNonAAFill(color, SkMatrix::I(),
369                                                                        dstRect, nullptr, &invert));
370    target->drawBatch(*pipelineBuilder, batch);
371}
372