blob: cbaae490c54e2e5eccbbc564e92ccc1ff85a463d [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/TiledTextureUtils.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkSize.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkDevice.h"
#include "src/core/SkImagePriv.h"
#include "src/core/SkSamplingPriv.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/image/SkImage_Base.h"
#if GR_TEST_UTILS
extern int gOverrideMaxTextureSize;
extern std::atomic<int> gNumTilesDrawn;
#endif
namespace {
void draw_tiled_bitmap_graphite(SkCanvas* canvas,
const SkBitmap& bitmap,
int tileSize,
const SkMatrix& srcToDst,
const SkRect& srcRect,
const SkIRect& clippedSrcIRect,
const SkPaint* paint,
SkCanvas::QuadAAFlags origAAFlags,
SkCanvas::SrcRectConstraint constraint,
SkSamplingOptions sampling) {
if (sampling.isAniso()) {
sampling = SkSamplingPriv::AnisoFallback(/* imageIsMipped= */ false);
}
SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect);
int nx = bitmap.width() / tileSize;
int ny = bitmap.height() / tileSize;
#if GR_TEST_UTILS
gNumTilesDrawn.store(0, std::memory_order_relaxed);
#endif
skia_private::TArray<SkCanvas::ImageSetEntry> imgSet(nx * ny);
for (int x = 0; x <= nx; x++) {
for (int y = 0; y <= ny; y++) {
SkRect tileR;
tileR.setLTRB(SkIntToScalar(x * tileSize), SkIntToScalar(y * tileSize),
SkIntToScalar((x + 1) * tileSize), SkIntToScalar((y + 1) * tileSize));
if (!SkRect::Intersects(tileR, clippedSrcRect)) {
continue;
}
if (!tileR.intersect(srcRect)) {
continue;
}
SkIRect iTileR;
tileR.roundOut(&iTileR);
SkVector offset = SkPoint::Make(SkIntToScalar(iTileR.fLeft),
SkIntToScalar(iTileR.fTop));
SkRect rectToDraw = tileR;
if (!srcToDst.mapRect(&rectToDraw)) {
continue;
}
if (sampling.filter != SkFilterMode::kNearest || sampling.useCubic) {
SkIRect iClampRect;
if (SkCanvas::kFast_SrcRectConstraint == constraint) {
// In bleed mode we want to always expand the tile on all edges
// but stay within the bitmap bounds
iClampRect = SkIRect::MakeWH(bitmap.width(), bitmap.height());
} else {
// In texture-domain/clamp mode we only want to expand the
// tile on edges interior to "srcRect" (i.e., we want to
// not bleed across the original clamped edges)
srcRect.roundOut(&iClampRect);
}
int outset = sampling.useCubic ? kBicubicFilterTexelPad : 1;
skgpu::TiledTextureUtils::ClampedOutsetWithOffset(&iTileR, outset, &offset,
iClampRect);
}
// We must subset as a bitmap and then turn it into an SkImage if we want caching to
// work. Image subsets always make a copy of the pixels and lose the association with
// the original's SkPixelRef.
if (SkBitmap subsetBmp; bitmap.extractSubset(&subsetBmp, iTileR)) {
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(subsetBmp,
kNever_SkCopyPixelsMode);
if (!image) {
continue;
}
unsigned aaFlags = SkCanvas::kNone_QuadAAFlags;
// Preserve the original edge AA flags for the exterior tile edges.
if (tileR.fLeft <= srcRect.fLeft && (origAAFlags & SkCanvas::kLeft_QuadAAFlag)) {
aaFlags |= SkCanvas::kLeft_QuadAAFlag;
}
if (tileR.fRight >= srcRect.fRight && (origAAFlags & SkCanvas::kRight_QuadAAFlag)) {
aaFlags |= SkCanvas::kRight_QuadAAFlag;
}
if (tileR.fTop <= srcRect.fTop && (origAAFlags & SkCanvas::kTop_QuadAAFlag)) {
aaFlags |= SkCanvas::kTop_QuadAAFlag;
}
if (tileR.fBottom >= srcRect.fBottom &&
(origAAFlags & SkCanvas::kBottom_QuadAAFlag)) {
aaFlags |= SkCanvas::kBottom_QuadAAFlag;
}
// Offset the source rect to make it "local" to our tmp bitmap
tileR.offset(-offset.fX, -offset.fY);
imgSet.push_back(SkCanvas::ImageSetEntry(std::move(image),
tileR,
rectToDraw,
/* matrixIndex= */ -1,
/* alpha= */ 1.0f,
aaFlags,
/* hasClip= */ false));
#if GR_TEST_UTILS
(void)gNumTilesDrawn.fetch_add(+1, std::memory_order_relaxed);
#endif
}
}
}
canvas->experimental_DrawEdgeAAImageSet(imgSet.data(),
imgSet.size(),
/* dstClips= */ nullptr,
/* preViewMatrices= */ nullptr,
sampling,
paint,
constraint);
}
} // anonymous namespace
namespace skgpu {
void TiledTextureUtils::DrawImageRect_Graphite(SkCanvas* canvas,
const SkImage* image,
const SkRect& srcRect,
const SkRect& dstRect,
const SkSamplingOptions& origSampling,
const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint) {
if (canvas->isClipEmpty()) {
return;
}
if (!image->isTextureBacked()) {
SkRect src;
SkRect dst;
SkMatrix srcToDst;
ImageDrawMode mode = OptimizeSampleArea(SkISize::Make(image->width(), image->height()),
srcRect, dstRect, /* dstClip= */ nullptr,
&src, &dst, &srcToDst);
if (mode == ImageDrawMode::kSkip) {
return;
}
SkASSERT(mode != ImageDrawMode::kDecal); // only happens if there is a 'dstClip'
if (src.contains(image->bounds())) {
constraint = SkCanvas::kFast_SrcRectConstraint;
}
SkBaseDevice* device = SkCanvasPriv::TopDevice(canvas);
const SkMatrix& localToDevice = device->localToDevice();
SkSamplingOptions sampling = origSampling;
if (sampling.mipmap != SkMipmapMode::kNone && CanDisableMipmap(localToDevice, srcToDst)) {
sampling = SkSamplingOptions(sampling.filter);
}
SkIRect clipRect = device->devClipBounds();
int tileFilterPad;
if (sampling.useCubic) {
tileFilterPad = kBicubicFilterTexelPad;
} else if (sampling.filter == SkFilterMode::kLinear || sampling.isAniso()) {
// Aniso will fallback to linear filtering in the tiling case.
tileFilterPad = 1;
} else {
tileFilterPad = 0;
}
auto caps = canvas->recorder()->priv().caps();
int maxTileSize = caps->maxTextureSize() - 2*tileFilterPad;
#if GR_TEST_UTILS
if (gOverrideMaxTextureSize) {
maxTileSize = gOverrideMaxTextureSize - 2 * tileFilterPad;
}
#endif
// TODO: enable the cacheSize-based tiling heuristic for Graphite. In this heuristic,
// if the texture would take up more than 50% of the cache but we really only need
// less than half of it, then split it into tiles.
size_t cacheSize = 0;
int tileSize;
SkIRect clippedSubset;
if (ShouldTileImage(clipRect,
image->dimensions(),
localToDevice,
srcToDst,
&src,
maxTileSize,
cacheSize,
&tileSize,
&clippedSubset)) {
// Extract pixels on the CPU, since we have to split into separate textures before
// sending to the GPU if tiling.
if (SkBitmap bm; as_IB(image)->getROPixels(nullptr, &bm)) {
draw_tiled_bitmap_graphite(canvas,
bm,
tileSize,
srcToDst,
src,
clippedSubset,
paint,
SkCanvas::kAll_QuadAAFlags,
constraint,
sampling);
return;
}
}
}
canvas->drawImageRect(image,
srcRect,
dstRect,
origSampling,
paint,
constraint);
}
} // namespace skgpu