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