xref: /aosp_15_r20/external/skia/src/gpu/TiledTextureUtils.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/TiledTextureUtils.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSamplingOptions.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkSafeMath.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkCanvasPriv.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkDevice.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkImagePriv.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkSamplingPriv.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/image/SkImage_Base.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/image/SkImage_Picture.h"
23*c8dee2aaSAndroid Build Coastguard Worker 
24*c8dee2aaSAndroid Build Coastguard Worker #include <functional>
25*c8dee2aaSAndroid Build Coastguard Worker 
26*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
27*c8dee2aaSAndroid Build Coastguard Worker //  Helper functions for tiling a large SkBitmap
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker namespace {
30*c8dee2aaSAndroid Build Coastguard Worker 
31*c8dee2aaSAndroid Build Coastguard Worker static const int kBmpSmallTileSize = 1 << 10;
32*c8dee2aaSAndroid Build Coastguard Worker 
get_tile_count(const SkIRect & srcRect,int tileSize)33*c8dee2aaSAndroid Build Coastguard Worker size_t get_tile_count(const SkIRect& srcRect, int tileSize)  {
34*c8dee2aaSAndroid Build Coastguard Worker     int tilesX = (srcRect.fRight / tileSize) - (srcRect.fLeft / tileSize) + 1;
35*c8dee2aaSAndroid Build Coastguard Worker     int tilesY = (srcRect.fBottom / tileSize) - (srcRect.fTop / tileSize) + 1;
36*c8dee2aaSAndroid Build Coastguard Worker     // We calculate expected tile count before we read the bitmap's pixels, so hypothetically we can
37*c8dee2aaSAndroid Build Coastguard Worker     // have lazy images with excessive dimensions that would cause (tilesX*tilesY) to overflow int.
38*c8dee2aaSAndroid Build Coastguard Worker     // In these situations we also later fail to allocate a bitmap to store the lazy image, so there
39*c8dee2aaSAndroid Build Coastguard Worker     // isn't really a performance concern around one image turning into millions of tiles.
40*c8dee2aaSAndroid Build Coastguard Worker     return SkSafeMath::Mul(tilesX, tilesY);
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker 
determine_tile_size(const SkIRect & src,int maxTileSize)43*c8dee2aaSAndroid Build Coastguard Worker int determine_tile_size(const SkIRect& src, int maxTileSize) {
44*c8dee2aaSAndroid Build Coastguard Worker     if (maxTileSize <= kBmpSmallTileSize) {
45*c8dee2aaSAndroid Build Coastguard Worker         return maxTileSize;
46*c8dee2aaSAndroid Build Coastguard Worker     }
47*c8dee2aaSAndroid Build Coastguard Worker 
48*c8dee2aaSAndroid Build Coastguard Worker     size_t maxTileTotalTileSize = get_tile_count(src, maxTileSize);
49*c8dee2aaSAndroid Build Coastguard Worker     size_t smallTotalTileSize = get_tile_count(src, kBmpSmallTileSize);
50*c8dee2aaSAndroid Build Coastguard Worker 
51*c8dee2aaSAndroid Build Coastguard Worker     maxTileTotalTileSize *= maxTileSize * maxTileSize;
52*c8dee2aaSAndroid Build Coastguard Worker     smallTotalTileSize *= kBmpSmallTileSize * kBmpSmallTileSize;
53*c8dee2aaSAndroid Build Coastguard Worker 
54*c8dee2aaSAndroid Build Coastguard Worker     if (maxTileTotalTileSize > 2 * smallTotalTileSize) {
55*c8dee2aaSAndroid Build Coastguard Worker         return kBmpSmallTileSize;
56*c8dee2aaSAndroid Build Coastguard Worker     } else {
57*c8dee2aaSAndroid Build Coastguard Worker         return maxTileSize;
58*c8dee2aaSAndroid Build Coastguard Worker     }
59*c8dee2aaSAndroid Build Coastguard Worker }
60*c8dee2aaSAndroid Build Coastguard Worker 
61*c8dee2aaSAndroid Build Coastguard Worker // Given a bitmap, an optional src rect, and a context with a clip and matrix determine what
62*c8dee2aaSAndroid Build Coastguard Worker // pixels from the bitmap are necessary.
determine_clipped_src_rect(SkIRect clippedSrcIRect,const SkMatrix & viewMatrix,const SkMatrix & srcToDstRect,const SkISize & imageDimensions,const SkRect * srcRectPtr)63*c8dee2aaSAndroid Build Coastguard Worker SkIRect determine_clipped_src_rect(SkIRect clippedSrcIRect,
64*c8dee2aaSAndroid Build Coastguard Worker                                    const SkMatrix& viewMatrix,
65*c8dee2aaSAndroid Build Coastguard Worker                                    const SkMatrix& srcToDstRect,
66*c8dee2aaSAndroid Build Coastguard Worker                                    const SkISize& imageDimensions,
67*c8dee2aaSAndroid Build Coastguard Worker                                    const SkRect* srcRectPtr) {
68*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix inv = SkMatrix::Concat(viewMatrix, srcToDstRect);
69*c8dee2aaSAndroid Build Coastguard Worker     if (!inv.invert(&inv)) {
70*c8dee2aaSAndroid Build Coastguard Worker         return SkIRect::MakeEmpty();
71*c8dee2aaSAndroid Build Coastguard Worker     }
72*c8dee2aaSAndroid Build Coastguard Worker     SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect);
73*c8dee2aaSAndroid Build Coastguard Worker     inv.mapRect(&clippedSrcRect);
74*c8dee2aaSAndroid Build Coastguard Worker     if (srcRectPtr) {
75*c8dee2aaSAndroid Build Coastguard Worker         if (!clippedSrcRect.intersect(*srcRectPtr)) {
76*c8dee2aaSAndroid Build Coastguard Worker             return SkIRect::MakeEmpty();
77*c8dee2aaSAndroid Build Coastguard Worker         }
78*c8dee2aaSAndroid Build Coastguard Worker     }
79*c8dee2aaSAndroid Build Coastguard Worker     clippedSrcRect.roundOut(&clippedSrcIRect);
80*c8dee2aaSAndroid Build Coastguard Worker     SkIRect bmpBounds = SkIRect::MakeSize(imageDimensions);
81*c8dee2aaSAndroid Build Coastguard Worker     if (!clippedSrcIRect.intersect(bmpBounds)) {
82*c8dee2aaSAndroid Build Coastguard Worker         return SkIRect::MakeEmpty();
83*c8dee2aaSAndroid Build Coastguard Worker     }
84*c8dee2aaSAndroid Build Coastguard Worker 
85*c8dee2aaSAndroid Build Coastguard Worker     return clippedSrcIRect;
86*c8dee2aaSAndroid Build Coastguard Worker }
87*c8dee2aaSAndroid Build Coastguard Worker 
draw_tiled_image(SkCanvas * canvas,std::function<sk_sp<SkImage> (SkIRect)> imageProc,SkISize originalSize,int tileSize,const SkMatrix & srcToDst,const SkRect & srcRect,const SkIRect & clippedSrcIRect,const SkPaint * paint,SkCanvas::QuadAAFlags origAAFlags,SkCanvas::SrcRectConstraint constraint,SkSamplingOptions sampling)88*c8dee2aaSAndroid Build Coastguard Worker int draw_tiled_image(SkCanvas* canvas,
89*c8dee2aaSAndroid Build Coastguard Worker                      std::function<sk_sp<SkImage>(SkIRect)> imageProc,
90*c8dee2aaSAndroid Build Coastguard Worker                      SkISize originalSize,
91*c8dee2aaSAndroid Build Coastguard Worker                      int tileSize,
92*c8dee2aaSAndroid Build Coastguard Worker                      const SkMatrix& srcToDst,
93*c8dee2aaSAndroid Build Coastguard Worker                      const SkRect& srcRect,
94*c8dee2aaSAndroid Build Coastguard Worker                      const SkIRect& clippedSrcIRect,
95*c8dee2aaSAndroid Build Coastguard Worker                      const SkPaint* paint,
96*c8dee2aaSAndroid Build Coastguard Worker                      SkCanvas::QuadAAFlags origAAFlags,
97*c8dee2aaSAndroid Build Coastguard Worker                      SkCanvas::SrcRectConstraint constraint,
98*c8dee2aaSAndroid Build Coastguard Worker                      SkSamplingOptions sampling) {
99*c8dee2aaSAndroid Build Coastguard Worker     if (sampling.isAniso()) {
100*c8dee2aaSAndroid Build Coastguard Worker         sampling = SkSamplingPriv::AnisoFallback(/* imageIsMipped= */ false);
101*c8dee2aaSAndroid Build Coastguard Worker     }
102*c8dee2aaSAndroid Build Coastguard Worker     SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect);
103*c8dee2aaSAndroid Build Coastguard Worker 
104*c8dee2aaSAndroid Build Coastguard Worker     int nx = originalSize.width() / tileSize;
105*c8dee2aaSAndroid Build Coastguard Worker     int ny = originalSize.height() / tileSize;
106*c8dee2aaSAndroid Build Coastguard Worker 
107*c8dee2aaSAndroid Build Coastguard Worker     int numTilesDrawn = 0;
108*c8dee2aaSAndroid Build Coastguard Worker 
109*c8dee2aaSAndroid Build Coastguard Worker     skia_private::TArray<SkCanvas::ImageSetEntry> imgSet(nx * ny);
110*c8dee2aaSAndroid Build Coastguard Worker 
111*c8dee2aaSAndroid Build Coastguard Worker     for (int x = 0; x <= nx; x++) {
112*c8dee2aaSAndroid Build Coastguard Worker         for (int y = 0; y <= ny; y++) {
113*c8dee2aaSAndroid Build Coastguard Worker             SkRect tileR;
114*c8dee2aaSAndroid Build Coastguard Worker             tileR.setLTRB(SkIntToScalar(x * tileSize),       SkIntToScalar(y * tileSize),
115*c8dee2aaSAndroid Build Coastguard Worker                           SkIntToScalar((x + 1) * tileSize), SkIntToScalar((y + 1) * tileSize));
116*c8dee2aaSAndroid Build Coastguard Worker 
117*c8dee2aaSAndroid Build Coastguard Worker             if (!SkRect::Intersects(tileR, clippedSrcRect)) {
118*c8dee2aaSAndroid Build Coastguard Worker                 continue;
119*c8dee2aaSAndroid Build Coastguard Worker             }
120*c8dee2aaSAndroid Build Coastguard Worker 
121*c8dee2aaSAndroid Build Coastguard Worker             if (!tileR.intersect(srcRect)) {
122*c8dee2aaSAndroid Build Coastguard Worker                 continue;
123*c8dee2aaSAndroid Build Coastguard Worker             }
124*c8dee2aaSAndroid Build Coastguard Worker 
125*c8dee2aaSAndroid Build Coastguard Worker             SkIRect iTileR;
126*c8dee2aaSAndroid Build Coastguard Worker             tileR.roundOut(&iTileR);
127*c8dee2aaSAndroid Build Coastguard Worker             SkVector offset = SkPoint::Make(SkIntToScalar(iTileR.fLeft),
128*c8dee2aaSAndroid Build Coastguard Worker                                             SkIntToScalar(iTileR.fTop));
129*c8dee2aaSAndroid Build Coastguard Worker             SkRect rectToDraw = tileR;
130*c8dee2aaSAndroid Build Coastguard Worker             if (!srcToDst.mapRect(&rectToDraw)) {
131*c8dee2aaSAndroid Build Coastguard Worker                 continue;
132*c8dee2aaSAndroid Build Coastguard Worker             }
133*c8dee2aaSAndroid Build Coastguard Worker 
134*c8dee2aaSAndroid Build Coastguard Worker             if (sampling.filter != SkFilterMode::kNearest || sampling.useCubic) {
135*c8dee2aaSAndroid Build Coastguard Worker                 SkIRect iClampRect;
136*c8dee2aaSAndroid Build Coastguard Worker 
137*c8dee2aaSAndroid Build Coastguard Worker                 if (SkCanvas::kFast_SrcRectConstraint == constraint) {
138*c8dee2aaSAndroid Build Coastguard Worker                     // In bleed mode we want to always expand the tile on all edges
139*c8dee2aaSAndroid Build Coastguard Worker                     // but stay within the bitmap bounds
140*c8dee2aaSAndroid Build Coastguard Worker                     iClampRect = SkIRect::MakeWH(originalSize.width(), originalSize.height());
141*c8dee2aaSAndroid Build Coastguard Worker                 } else {
142*c8dee2aaSAndroid Build Coastguard Worker                     // In texture-domain/clamp mode we only want to expand the
143*c8dee2aaSAndroid Build Coastguard Worker                     // tile on edges interior to "srcRect" (i.e., we want to
144*c8dee2aaSAndroid Build Coastguard Worker                     // not bleed across the original clamped edges)
145*c8dee2aaSAndroid Build Coastguard Worker                     srcRect.roundOut(&iClampRect);
146*c8dee2aaSAndroid Build Coastguard Worker                 }
147*c8dee2aaSAndroid Build Coastguard Worker                 int outset = sampling.useCubic ? kBicubicFilterTexelPad : 1;
148*c8dee2aaSAndroid Build Coastguard Worker                 skgpu::TiledTextureUtils::ClampedOutsetWithOffset(&iTileR, outset, &offset,
149*c8dee2aaSAndroid Build Coastguard Worker                                                                   iClampRect);
150*c8dee2aaSAndroid Build Coastguard Worker             }
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker             sk_sp<SkImage> image = imageProc(iTileR);
153*c8dee2aaSAndroid Build Coastguard Worker             if (!image) {
154*c8dee2aaSAndroid Build Coastguard Worker                 continue;
155*c8dee2aaSAndroid Build Coastguard Worker             }
156*c8dee2aaSAndroid Build Coastguard Worker 
157*c8dee2aaSAndroid Build Coastguard Worker             unsigned aaFlags = SkCanvas::kNone_QuadAAFlags;
158*c8dee2aaSAndroid Build Coastguard Worker             // Preserve the original edge AA flags for the exterior tile edges.
159*c8dee2aaSAndroid Build Coastguard Worker             if (tileR.fLeft <= srcRect.fLeft && (origAAFlags & SkCanvas::kLeft_QuadAAFlag)) {
160*c8dee2aaSAndroid Build Coastguard Worker                 aaFlags |= SkCanvas::kLeft_QuadAAFlag;
161*c8dee2aaSAndroid Build Coastguard Worker             }
162*c8dee2aaSAndroid Build Coastguard Worker             if (tileR.fRight >= srcRect.fRight && (origAAFlags & SkCanvas::kRight_QuadAAFlag)) {
163*c8dee2aaSAndroid Build Coastguard Worker                 aaFlags |= SkCanvas::kRight_QuadAAFlag;
164*c8dee2aaSAndroid Build Coastguard Worker             }
165*c8dee2aaSAndroid Build Coastguard Worker             if (tileR.fTop <= srcRect.fTop && (origAAFlags & SkCanvas::kTop_QuadAAFlag)) {
166*c8dee2aaSAndroid Build Coastguard Worker                 aaFlags |= SkCanvas::kTop_QuadAAFlag;
167*c8dee2aaSAndroid Build Coastguard Worker             }
168*c8dee2aaSAndroid Build Coastguard Worker             if (tileR.fBottom >= srcRect.fBottom &&
169*c8dee2aaSAndroid Build Coastguard Worker                 (origAAFlags & SkCanvas::kBottom_QuadAAFlag)) {
170*c8dee2aaSAndroid Build Coastguard Worker                 aaFlags |= SkCanvas::kBottom_QuadAAFlag;
171*c8dee2aaSAndroid Build Coastguard Worker             }
172*c8dee2aaSAndroid Build Coastguard Worker 
173*c8dee2aaSAndroid Build Coastguard Worker             // Offset the source rect to make it "local" to our tmp bitmap
174*c8dee2aaSAndroid Build Coastguard Worker             tileR.offset(-offset.fX, -offset.fY);
175*c8dee2aaSAndroid Build Coastguard Worker 
176*c8dee2aaSAndroid Build Coastguard Worker             imgSet.push_back(SkCanvas::ImageSetEntry(std::move(image),
177*c8dee2aaSAndroid Build Coastguard Worker                                                      tileR,
178*c8dee2aaSAndroid Build Coastguard Worker                                                      rectToDraw,
179*c8dee2aaSAndroid Build Coastguard Worker                                                      /* matrixIndex= */ -1,
180*c8dee2aaSAndroid Build Coastguard Worker                                                      /* alpha= */ 1.0f,
181*c8dee2aaSAndroid Build Coastguard Worker                                                      aaFlags,
182*c8dee2aaSAndroid Build Coastguard Worker                                                      /* hasClip= */ false));
183*c8dee2aaSAndroid Build Coastguard Worker 
184*c8dee2aaSAndroid Build Coastguard Worker             numTilesDrawn += 1;
185*c8dee2aaSAndroid Build Coastguard Worker         }
186*c8dee2aaSAndroid Build Coastguard Worker     }
187*c8dee2aaSAndroid Build Coastguard Worker 
188*c8dee2aaSAndroid Build Coastguard Worker     canvas->experimental_DrawEdgeAAImageSet(imgSet.data(),
189*c8dee2aaSAndroid Build Coastguard Worker                                             imgSet.size(),
190*c8dee2aaSAndroid Build Coastguard Worker                                             /* dstClips= */ nullptr,
191*c8dee2aaSAndroid Build Coastguard Worker                                             /* preViewMatrices= */ nullptr,
192*c8dee2aaSAndroid Build Coastguard Worker                                             sampling,
193*c8dee2aaSAndroid Build Coastguard Worker                                             paint,
194*c8dee2aaSAndroid Build Coastguard Worker                                             constraint);
195*c8dee2aaSAndroid Build Coastguard Worker     return numTilesDrawn;
196*c8dee2aaSAndroid Build Coastguard Worker }
197*c8dee2aaSAndroid Build Coastguard Worker 
198*c8dee2aaSAndroid Build Coastguard Worker } // anonymous namespace
199*c8dee2aaSAndroid Build Coastguard Worker 
200*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu {
201*c8dee2aaSAndroid Build Coastguard Worker 
202*c8dee2aaSAndroid Build Coastguard Worker // tileSize and clippedSubset are valid if true is returned
ShouldTileImage(SkIRect conservativeClipBounds,const SkISize & imageSize,const SkMatrix & ctm,const SkMatrix & srcToDst,const SkRect * src,int maxTileSize,size_t cacheSize,int * tileSize,SkIRect * clippedSubset)203*c8dee2aaSAndroid Build Coastguard Worker bool TiledTextureUtils::ShouldTileImage(SkIRect conservativeClipBounds,
204*c8dee2aaSAndroid Build Coastguard Worker                                         const SkISize& imageSize,
205*c8dee2aaSAndroid Build Coastguard Worker                                         const SkMatrix& ctm,
206*c8dee2aaSAndroid Build Coastguard Worker                                         const SkMatrix& srcToDst,
207*c8dee2aaSAndroid Build Coastguard Worker                                         const SkRect* src,
208*c8dee2aaSAndroid Build Coastguard Worker                                         int maxTileSize,
209*c8dee2aaSAndroid Build Coastguard Worker                                         size_t cacheSize,
210*c8dee2aaSAndroid Build Coastguard Worker                                         int* tileSize,
211*c8dee2aaSAndroid Build Coastguard Worker                                         SkIRect* clippedSubset) {
212*c8dee2aaSAndroid Build Coastguard Worker     // if it's larger than the max tile size, then we have no choice but tiling.
213*c8dee2aaSAndroid Build Coastguard Worker     if (imageSize.width() > maxTileSize || imageSize.height() > maxTileSize) {
214*c8dee2aaSAndroid Build Coastguard Worker         *clippedSubset = determine_clipped_src_rect(conservativeClipBounds, ctm,
215*c8dee2aaSAndroid Build Coastguard Worker                                                     srcToDst, imageSize, src);
216*c8dee2aaSAndroid Build Coastguard Worker         *tileSize = determine_tile_size(*clippedSubset, maxTileSize);
217*c8dee2aaSAndroid Build Coastguard Worker         return true;
218*c8dee2aaSAndroid Build Coastguard Worker     }
219*c8dee2aaSAndroid Build Coastguard Worker 
220*c8dee2aaSAndroid Build Coastguard Worker     // If the image would only produce 4 tiles of the smaller size, don't bother tiling it.
221*c8dee2aaSAndroid Build Coastguard Worker     const size_t area = imageSize.width() * imageSize.height();
222*c8dee2aaSAndroid Build Coastguard Worker     if (area < 4 * kBmpSmallTileSize * kBmpSmallTileSize) {
223*c8dee2aaSAndroid Build Coastguard Worker         return false;
224*c8dee2aaSAndroid Build Coastguard Worker     }
225*c8dee2aaSAndroid Build Coastguard Worker 
226*c8dee2aaSAndroid Build Coastguard Worker     // At this point we know we could do the draw by uploading the entire bitmap as a texture.
227*c8dee2aaSAndroid Build Coastguard Worker     // However, if the texture would be large compared to the cache size and we don't require most
228*c8dee2aaSAndroid Build Coastguard Worker     // of it for this draw then tile to reduce the amount of upload and cache spill.
229*c8dee2aaSAndroid Build Coastguard Worker     if (!cacheSize) {
230*c8dee2aaSAndroid Build Coastguard Worker         // We don't have access to the cacheSize so we will just upload the entire image
231*c8dee2aaSAndroid Build Coastguard Worker         // to be on the safe side and not tile.
232*c8dee2aaSAndroid Build Coastguard Worker         return false;
233*c8dee2aaSAndroid Build Coastguard Worker     }
234*c8dee2aaSAndroid Build Coastguard Worker 
235*c8dee2aaSAndroid Build Coastguard Worker     // An assumption here is that sw bitmap size is a good proxy for its size as a texture
236*c8dee2aaSAndroid Build Coastguard Worker     size_t bmpSize = area * sizeof(SkPMColor);  // assume 32bit pixels
237*c8dee2aaSAndroid Build Coastguard Worker     if (bmpSize < cacheSize / 2) {
238*c8dee2aaSAndroid Build Coastguard Worker         return false;
239*c8dee2aaSAndroid Build Coastguard Worker     }
240*c8dee2aaSAndroid Build Coastguard Worker 
241*c8dee2aaSAndroid Build Coastguard Worker     // Figure out how much of the src we will need based on the src rect and clipping. Reject if
242*c8dee2aaSAndroid Build Coastguard Worker     // tiling memory savings would be < 50%.
243*c8dee2aaSAndroid Build Coastguard Worker     *clippedSubset = determine_clipped_src_rect(conservativeClipBounds, ctm,
244*c8dee2aaSAndroid Build Coastguard Worker                                                 srcToDst, imageSize, src);
245*c8dee2aaSAndroid Build Coastguard Worker     *tileSize = kBmpSmallTileSize; // already know whole bitmap fits in one max sized tile.
246*c8dee2aaSAndroid Build Coastguard Worker     size_t usedTileBytes = get_tile_count(*clippedSubset, kBmpSmallTileSize) *
247*c8dee2aaSAndroid Build Coastguard Worker                            kBmpSmallTileSize * kBmpSmallTileSize *
248*c8dee2aaSAndroid Build Coastguard Worker                            sizeof(SkPMColor);  // assume 32bit pixels;
249*c8dee2aaSAndroid Build Coastguard Worker 
250*c8dee2aaSAndroid Build Coastguard Worker     return usedTileBytes * 2 < bmpSize;
251*c8dee2aaSAndroid Build Coastguard Worker }
252*c8dee2aaSAndroid Build Coastguard Worker 
253*c8dee2aaSAndroid Build Coastguard Worker /**
254*c8dee2aaSAndroid Build Coastguard Worker  * Optimize the src rect sampling area within an image (sized 'width' x 'height') such that
255*c8dee2aaSAndroid Build Coastguard Worker  * 'outSrcRect' will be completely contained in the image's bounds. The corresponding rect
256*c8dee2aaSAndroid Build Coastguard Worker  * to draw will be output to 'outDstRect'. The mapping between src and dst will be cached in
257*c8dee2aaSAndroid Build Coastguard Worker  * 'outSrcToDst'. Outputs are not always updated when kSkip is returned.
258*c8dee2aaSAndroid Build Coastguard Worker  *
259*c8dee2aaSAndroid Build Coastguard Worker  * 'dstClip' should be null when there is no additional clipping.
260*c8dee2aaSAndroid Build Coastguard Worker  */
OptimizeSampleArea(const SkISize & imageSize,const SkRect & origSrcRect,const SkRect & origDstRect,const SkPoint dstClip[4],SkRect * outSrcRect,SkRect * outDstRect,SkMatrix * outSrcToDst)261*c8dee2aaSAndroid Build Coastguard Worker TiledTextureUtils::ImageDrawMode TiledTextureUtils::OptimizeSampleArea(const SkISize& imageSize,
262*c8dee2aaSAndroid Build Coastguard Worker                                                                        const SkRect& origSrcRect,
263*c8dee2aaSAndroid Build Coastguard Worker                                                                        const SkRect& origDstRect,
264*c8dee2aaSAndroid Build Coastguard Worker                                                                        const SkPoint dstClip[4],
265*c8dee2aaSAndroid Build Coastguard Worker                                                                        SkRect* outSrcRect,
266*c8dee2aaSAndroid Build Coastguard Worker                                                                        SkRect* outDstRect,
267*c8dee2aaSAndroid Build Coastguard Worker                                                                        SkMatrix* outSrcToDst) {
268*c8dee2aaSAndroid Build Coastguard Worker     if (origSrcRect.isEmpty() || origDstRect.isEmpty()) {
269*c8dee2aaSAndroid Build Coastguard Worker         return ImageDrawMode::kSkip;
270*c8dee2aaSAndroid Build Coastguard Worker     }
271*c8dee2aaSAndroid Build Coastguard Worker 
272*c8dee2aaSAndroid Build Coastguard Worker     *outSrcToDst = SkMatrix::RectToRect(origSrcRect, origDstRect);
273*c8dee2aaSAndroid Build Coastguard Worker 
274*c8dee2aaSAndroid Build Coastguard Worker     SkRect src = origSrcRect;
275*c8dee2aaSAndroid Build Coastguard Worker     SkRect dst = origDstRect;
276*c8dee2aaSAndroid Build Coastguard Worker 
277*c8dee2aaSAndroid Build Coastguard Worker     const SkRect srcBounds = SkRect::Make(imageSize);
278*c8dee2aaSAndroid Build Coastguard Worker 
279*c8dee2aaSAndroid Build Coastguard Worker     if (!srcBounds.contains(src)) {
280*c8dee2aaSAndroid Build Coastguard Worker         if (!src.intersect(srcBounds)) {
281*c8dee2aaSAndroid Build Coastguard Worker             return ImageDrawMode::kSkip;
282*c8dee2aaSAndroid Build Coastguard Worker         }
283*c8dee2aaSAndroid Build Coastguard Worker         outSrcToDst->mapRect(&dst, src);
284*c8dee2aaSAndroid Build Coastguard Worker 
285*c8dee2aaSAndroid Build Coastguard Worker         // Both src and dst have gotten smaller. If dstClip is provided, confirm it is still
286*c8dee2aaSAndroid Build Coastguard Worker         // contained in dst, otherwise cannot optimize the sample area and must use a decal instead
287*c8dee2aaSAndroid Build Coastguard Worker         if (dstClip) {
288*c8dee2aaSAndroid Build Coastguard Worker             for (int i = 0; i < 4; ++i) {
289*c8dee2aaSAndroid Build Coastguard Worker                 if (!dst.contains(dstClip[i].fX, dstClip[i].fY)) {
290*c8dee2aaSAndroid Build Coastguard Worker                     // Must resort to using a decal mode restricted to the clipped 'src', and
291*c8dee2aaSAndroid Build Coastguard Worker                     // use the original dst rect (filling in src bounds as needed)
292*c8dee2aaSAndroid Build Coastguard Worker                     *outSrcRect = src;
293*c8dee2aaSAndroid Build Coastguard Worker                     *outDstRect = origDstRect;
294*c8dee2aaSAndroid Build Coastguard Worker                     return ImageDrawMode::kDecal;
295*c8dee2aaSAndroid Build Coastguard Worker                 }
296*c8dee2aaSAndroid Build Coastguard Worker             }
297*c8dee2aaSAndroid Build Coastguard Worker         }
298*c8dee2aaSAndroid Build Coastguard Worker     }
299*c8dee2aaSAndroid Build Coastguard Worker 
300*c8dee2aaSAndroid Build Coastguard Worker     // The original src and dst were fully contained in the image, or there was no dst clip to
301*c8dee2aaSAndroid Build Coastguard Worker     // worry about, or the clip was still contained in the restricted dst rect.
302*c8dee2aaSAndroid Build Coastguard Worker     *outSrcRect = src;
303*c8dee2aaSAndroid Build Coastguard Worker     *outDstRect = dst;
304*c8dee2aaSAndroid Build Coastguard Worker     return ImageDrawMode::kOptimized;
305*c8dee2aaSAndroid Build Coastguard Worker }
306*c8dee2aaSAndroid Build Coastguard Worker 
CanDisableMipmap(const SkMatrix & viewM,const SkMatrix & localM,bool sharpenMipmappedTextures)307*c8dee2aaSAndroid Build Coastguard Worker bool TiledTextureUtils::CanDisableMipmap(const SkMatrix& viewM,
308*c8dee2aaSAndroid Build Coastguard Worker                                          const SkMatrix& localM,
309*c8dee2aaSAndroid Build Coastguard Worker                                          bool sharpenMipmappedTextures) {
310*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix matrix;
311*c8dee2aaSAndroid Build Coastguard Worker     matrix.setConcat(viewM, localM);
312*c8dee2aaSAndroid Build Coastguard Worker     // With sharp mips, we bias mipmap lookups by -0.5. That means our final LOD is >= 0 until
313*c8dee2aaSAndroid Build Coastguard Worker     // the computed LOD is >= 0.5. At what scale factor does a texture get an LOD of
314*c8dee2aaSAndroid Build Coastguard Worker     // 0.5?
315*c8dee2aaSAndroid Build Coastguard Worker     //
316*c8dee2aaSAndroid Build Coastguard Worker     // Want:  0       = log2(1/s) - 0.5
317*c8dee2aaSAndroid Build Coastguard Worker     //        0.5     = log2(1/s)
318*c8dee2aaSAndroid Build Coastguard Worker     //        2^0.5   = 1/s
319*c8dee2aaSAndroid Build Coastguard Worker     //        1/2^0.5 = s
320*c8dee2aaSAndroid Build Coastguard Worker     //        2^0.5/2 = s
321*c8dee2aaSAndroid Build Coastguard Worker     SkScalar mipScale = sharpenMipmappedTextures ? SK_ScalarRoot2Over2 : SK_Scalar1;
322*c8dee2aaSAndroid Build Coastguard Worker     return matrix.getMinScale() >= mipScale;
323*c8dee2aaSAndroid Build Coastguard Worker }
324*c8dee2aaSAndroid Build Coastguard Worker 
325*c8dee2aaSAndroid Build Coastguard Worker 
326*c8dee2aaSAndroid Build Coastguard Worker // This method outsets 'iRect' by 'outset' all around and then clamps its extents to
327*c8dee2aaSAndroid Build Coastguard Worker // 'clamp'. 'offset' is adjusted to remain positioned over the top-left corner
328*c8dee2aaSAndroid Build Coastguard Worker // of 'iRect' for all possible outsets/clamps.
ClampedOutsetWithOffset(SkIRect * iRect,int outset,SkPoint * offset,const SkIRect & clamp)329*c8dee2aaSAndroid Build Coastguard Worker void TiledTextureUtils::ClampedOutsetWithOffset(SkIRect* iRect, int outset, SkPoint* offset,
330*c8dee2aaSAndroid Build Coastguard Worker                                                 const SkIRect& clamp) {
331*c8dee2aaSAndroid Build Coastguard Worker     iRect->outset(outset, outset);
332*c8dee2aaSAndroid Build Coastguard Worker 
333*c8dee2aaSAndroid Build Coastguard Worker     int leftClampDelta = clamp.fLeft - iRect->fLeft;
334*c8dee2aaSAndroid Build Coastguard Worker     if (leftClampDelta > 0) {
335*c8dee2aaSAndroid Build Coastguard Worker         offset->fX -= outset - leftClampDelta;
336*c8dee2aaSAndroid Build Coastguard Worker         iRect->fLeft = clamp.fLeft;
337*c8dee2aaSAndroid Build Coastguard Worker     } else {
338*c8dee2aaSAndroid Build Coastguard Worker         offset->fX -= outset;
339*c8dee2aaSAndroid Build Coastguard Worker     }
340*c8dee2aaSAndroid Build Coastguard Worker 
341*c8dee2aaSAndroid Build Coastguard Worker     int topClampDelta = clamp.fTop - iRect->fTop;
342*c8dee2aaSAndroid Build Coastguard Worker     if (topClampDelta > 0) {
343*c8dee2aaSAndroid Build Coastguard Worker         offset->fY -= outset - topClampDelta;
344*c8dee2aaSAndroid Build Coastguard Worker         iRect->fTop = clamp.fTop;
345*c8dee2aaSAndroid Build Coastguard Worker     } else {
346*c8dee2aaSAndroid Build Coastguard Worker         offset->fY -= outset;
347*c8dee2aaSAndroid Build Coastguard Worker     }
348*c8dee2aaSAndroid Build Coastguard Worker 
349*c8dee2aaSAndroid Build Coastguard Worker     if (iRect->fRight > clamp.fRight) {
350*c8dee2aaSAndroid Build Coastguard Worker         iRect->fRight = clamp.fRight;
351*c8dee2aaSAndroid Build Coastguard Worker     }
352*c8dee2aaSAndroid Build Coastguard Worker     if (iRect->fBottom > clamp.fBottom) {
353*c8dee2aaSAndroid Build Coastguard Worker         iRect->fBottom = clamp.fBottom;
354*c8dee2aaSAndroid Build Coastguard Worker     }
355*c8dee2aaSAndroid Build Coastguard Worker }
356*c8dee2aaSAndroid Build Coastguard Worker 
DrawAsTiledImageRect(SkCanvas * canvas,const SkImage * image,const SkRect & srcRect,const SkRect & dstRect,SkCanvas::QuadAAFlags aaFlags,const SkSamplingOptions & origSampling,const SkPaint * paint,SkCanvas::SrcRectConstraint constraint,bool sharpenMM,size_t cacheSize,size_t maxTextureSize)357*c8dee2aaSAndroid Build Coastguard Worker std::tuple<bool, size_t> TiledTextureUtils::DrawAsTiledImageRect(
358*c8dee2aaSAndroid Build Coastguard Worker         SkCanvas* canvas,
359*c8dee2aaSAndroid Build Coastguard Worker         const SkImage* image,
360*c8dee2aaSAndroid Build Coastguard Worker         const SkRect& srcRect,
361*c8dee2aaSAndroid Build Coastguard Worker         const SkRect& dstRect,
362*c8dee2aaSAndroid Build Coastguard Worker         SkCanvas::QuadAAFlags aaFlags,
363*c8dee2aaSAndroid Build Coastguard Worker         const SkSamplingOptions& origSampling,
364*c8dee2aaSAndroid Build Coastguard Worker         const SkPaint* paint,
365*c8dee2aaSAndroid Build Coastguard Worker         SkCanvas::SrcRectConstraint constraint,
366*c8dee2aaSAndroid Build Coastguard Worker         bool sharpenMM,
367*c8dee2aaSAndroid Build Coastguard Worker         size_t cacheSize,
368*c8dee2aaSAndroid Build Coastguard Worker         size_t maxTextureSize) {
369*c8dee2aaSAndroid Build Coastguard Worker     if (canvas->isClipEmpty()) {
370*c8dee2aaSAndroid Build Coastguard Worker         return {true, 0};
371*c8dee2aaSAndroid Build Coastguard Worker     }
372*c8dee2aaSAndroid Build Coastguard Worker 
373*c8dee2aaSAndroid Build Coastguard Worker     if (!image->isTextureBacked()) {
374*c8dee2aaSAndroid Build Coastguard Worker         SkRect src;
375*c8dee2aaSAndroid Build Coastguard Worker         SkRect dst;
376*c8dee2aaSAndroid Build Coastguard Worker         SkMatrix srcToDst;
377*c8dee2aaSAndroid Build Coastguard Worker         ImageDrawMode mode = OptimizeSampleArea(SkISize::Make(image->width(), image->height()),
378*c8dee2aaSAndroid Build Coastguard Worker                                                 srcRect, dstRect, /* dstClip= */ nullptr,
379*c8dee2aaSAndroid Build Coastguard Worker                                                 &src, &dst, &srcToDst);
380*c8dee2aaSAndroid Build Coastguard Worker         if (mode == ImageDrawMode::kSkip) {
381*c8dee2aaSAndroid Build Coastguard Worker             return {true, 0};
382*c8dee2aaSAndroid Build Coastguard Worker         }
383*c8dee2aaSAndroid Build Coastguard Worker 
384*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(mode != ImageDrawMode::kDecal); // only happens if there is a 'dstClip'
385*c8dee2aaSAndroid Build Coastguard Worker 
386*c8dee2aaSAndroid Build Coastguard Worker         if (src.contains(image->bounds())) {
387*c8dee2aaSAndroid Build Coastguard Worker             constraint = SkCanvas::kFast_SrcRectConstraint;
388*c8dee2aaSAndroid Build Coastguard Worker         }
389*c8dee2aaSAndroid Build Coastguard Worker 
390*c8dee2aaSAndroid Build Coastguard Worker         SkDevice* device = SkCanvasPriv::TopDevice(canvas);
391*c8dee2aaSAndroid Build Coastguard Worker         const SkMatrix& localToDevice = device->localToDevice();
392*c8dee2aaSAndroid Build Coastguard Worker 
393*c8dee2aaSAndroid Build Coastguard Worker         SkSamplingOptions sampling = origSampling;
394*c8dee2aaSAndroid Build Coastguard Worker         if (sampling.mipmap != SkMipmapMode::kNone &&
395*c8dee2aaSAndroid Build Coastguard Worker             CanDisableMipmap(localToDevice, srcToDst, sharpenMM)) {
396*c8dee2aaSAndroid Build Coastguard Worker             sampling = SkSamplingOptions(sampling.filter);
397*c8dee2aaSAndroid Build Coastguard Worker         }
398*c8dee2aaSAndroid Build Coastguard Worker 
399*c8dee2aaSAndroid Build Coastguard Worker         SkIRect clipRect = device->devClipBounds();
400*c8dee2aaSAndroid Build Coastguard Worker 
401*c8dee2aaSAndroid Build Coastguard Worker         int tileFilterPad;
402*c8dee2aaSAndroid Build Coastguard Worker         if (sampling.useCubic) {
403*c8dee2aaSAndroid Build Coastguard Worker             tileFilterPad = kBicubicFilterTexelPad;
404*c8dee2aaSAndroid Build Coastguard Worker         } else if (sampling.filter == SkFilterMode::kLinear || sampling.isAniso()) {
405*c8dee2aaSAndroid Build Coastguard Worker             // Aniso will fallback to linear filtering in the tiling case.
406*c8dee2aaSAndroid Build Coastguard Worker             tileFilterPad = 1;
407*c8dee2aaSAndroid Build Coastguard Worker         } else {
408*c8dee2aaSAndroid Build Coastguard Worker             tileFilterPad = 0;
409*c8dee2aaSAndroid Build Coastguard Worker         }
410*c8dee2aaSAndroid Build Coastguard Worker 
411*c8dee2aaSAndroid Build Coastguard Worker         int maxTileSize = maxTextureSize - 2 * tileFilterPad;
412*c8dee2aaSAndroid Build Coastguard Worker         int tileSize;
413*c8dee2aaSAndroid Build Coastguard Worker         SkIRect clippedSubset;
414*c8dee2aaSAndroid Build Coastguard Worker         if (ShouldTileImage(clipRect,
415*c8dee2aaSAndroid Build Coastguard Worker                             image->dimensions(),
416*c8dee2aaSAndroid Build Coastguard Worker                             localToDevice,
417*c8dee2aaSAndroid Build Coastguard Worker                             srcToDst,
418*c8dee2aaSAndroid Build Coastguard Worker                             &src,
419*c8dee2aaSAndroid Build Coastguard Worker                             maxTileSize,
420*c8dee2aaSAndroid Build Coastguard Worker                             cacheSize,
421*c8dee2aaSAndroid Build Coastguard Worker                             &tileSize,
422*c8dee2aaSAndroid Build Coastguard Worker                             &clippedSubset)) {
423*c8dee2aaSAndroid Build Coastguard Worker             // If it's a Picture-backed image we should subset the SkPicture directly rather than
424*c8dee2aaSAndroid Build Coastguard Worker             // converting to a Bitmap and then subsetting. Rendering to a bitmap will use a Raster
425*c8dee2aaSAndroid Build Coastguard Worker             // surface, and the SkPicture could have GPU data.
426*c8dee2aaSAndroid Build Coastguard Worker             if (as_IB(image)->type() == SkImage_Base::Type::kLazyPicture) {
427*c8dee2aaSAndroid Build Coastguard Worker                 auto imageProc = [&](SkIRect iTileR) {
428*c8dee2aaSAndroid Build Coastguard Worker                     return image->makeSubset(nullptr, iTileR);
429*c8dee2aaSAndroid Build Coastguard Worker                 };
430*c8dee2aaSAndroid Build Coastguard Worker 
431*c8dee2aaSAndroid Build Coastguard Worker                 size_t tiles = draw_tiled_image(canvas,
432*c8dee2aaSAndroid Build Coastguard Worker                                                 imageProc,
433*c8dee2aaSAndroid Build Coastguard Worker                                                 image->dimensions(),
434*c8dee2aaSAndroid Build Coastguard Worker                                                 tileSize,
435*c8dee2aaSAndroid Build Coastguard Worker                                                 srcToDst,
436*c8dee2aaSAndroid Build Coastguard Worker                                                 src,
437*c8dee2aaSAndroid Build Coastguard Worker                                                 clippedSubset,
438*c8dee2aaSAndroid Build Coastguard Worker                                                 paint,
439*c8dee2aaSAndroid Build Coastguard Worker                                                 aaFlags,
440*c8dee2aaSAndroid Build Coastguard Worker                                                 constraint,
441*c8dee2aaSAndroid Build Coastguard Worker                                                 sampling);
442*c8dee2aaSAndroid Build Coastguard Worker                 return {true, tiles};
443*c8dee2aaSAndroid Build Coastguard Worker             }
444*c8dee2aaSAndroid Build Coastguard Worker 
445*c8dee2aaSAndroid Build Coastguard Worker             // Extract pixels on the CPU, since we have to split into separate textures before
446*c8dee2aaSAndroid Build Coastguard Worker             // sending to the GPU if tiling.
447*c8dee2aaSAndroid Build Coastguard Worker             if (SkBitmap bm; as_IB(image)->getROPixels(nullptr, &bm)) {
448*c8dee2aaSAndroid Build Coastguard Worker                 auto imageProc = [&](SkIRect iTileR) {
449*c8dee2aaSAndroid Build Coastguard Worker                     // We must subset as a bitmap and then turn it into an SkImage if we want
450*c8dee2aaSAndroid Build Coastguard Worker                     // caching to work. Image subsets always make a copy of the pixels and lose
451*c8dee2aaSAndroid Build Coastguard Worker                     // the association with the original's SkPixelRef.
452*c8dee2aaSAndroid Build Coastguard Worker                     if (SkBitmap subsetBmp; bm.extractSubset(&subsetBmp, iTileR)) {
453*c8dee2aaSAndroid Build Coastguard Worker                         return SkMakeImageFromRasterBitmap(subsetBmp, kNever_SkCopyPixelsMode);
454*c8dee2aaSAndroid Build Coastguard Worker                     }
455*c8dee2aaSAndroid Build Coastguard Worker                     return sk_sp<SkImage>(nullptr);
456*c8dee2aaSAndroid Build Coastguard Worker                 };
457*c8dee2aaSAndroid Build Coastguard Worker 
458*c8dee2aaSAndroid Build Coastguard Worker                 size_t tiles = draw_tiled_image(canvas,
459*c8dee2aaSAndroid Build Coastguard Worker                                                 imageProc,
460*c8dee2aaSAndroid Build Coastguard Worker                                                 bm.dimensions(),
461*c8dee2aaSAndroid Build Coastguard Worker                                                 tileSize,
462*c8dee2aaSAndroid Build Coastguard Worker                                                 srcToDst,
463*c8dee2aaSAndroid Build Coastguard Worker                                                 src,
464*c8dee2aaSAndroid Build Coastguard Worker                                                 clippedSubset,
465*c8dee2aaSAndroid Build Coastguard Worker                                                 paint,
466*c8dee2aaSAndroid Build Coastguard Worker                                                 aaFlags,
467*c8dee2aaSAndroid Build Coastguard Worker                                                 constraint,
468*c8dee2aaSAndroid Build Coastguard Worker                                                 sampling);
469*c8dee2aaSAndroid Build Coastguard Worker                 return {true, tiles};
470*c8dee2aaSAndroid Build Coastguard Worker             }
471*c8dee2aaSAndroid Build Coastguard Worker         }
472*c8dee2aaSAndroid Build Coastguard Worker     }
473*c8dee2aaSAndroid Build Coastguard Worker 
474*c8dee2aaSAndroid Build Coastguard Worker     return {false, 0};
475*c8dee2aaSAndroid Build Coastguard Worker }
476*c8dee2aaSAndroid Build Coastguard Worker 
477*c8dee2aaSAndroid Build Coastguard Worker } // namespace skgpu
478