blob: cb6d6352e9cdf11be4b1bbd2cfa11d73384d10ca [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/graphite/RasterPathAtlas.h"
#include "include/core/SkColorSpace.h"
#include "src/core/SkBlitter_A8.h"
#include "src/core/SkDrawBase.h"
#include "src/core/SkRasterClip.h"
#include "src/gpu/graphite/DrawContext.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/TextureProxy.h"
namespace skgpu::graphite {
namespace {
// TODO: select atlas size dynamically? Take ContextOptions::fMaxTextureAtlasSize into account?
// TODO: for now
constexpr uint16_t kAtlasDim = 4096;
} // namespace
RasterPathAtlas::RasterPathAtlas() : PathAtlas(kAtlasDim, kAtlasDim) {}
void RasterPathAtlas::recordUploads(DrawContext* dc, Recorder* recorder) {
// build an upload for the dirty rect and record it
if (!fDirtyRect.isEmpty()) {
std::vector<MipLevel> levels;
levels.push_back({fPixels.addr(), fPixels.rowBytes()});
SkColorInfo colorInfo(kAlpha_8_SkColorType, kUnknown_SkAlphaType, nullptr);
SkASSERT(this->texture());
if (!dc->recordUpload(recorder, sk_ref_sp(this->texture()), colorInfo, colorInfo, levels,
fDirtyRect, nullptr)) {
SKGPU_LOG_W("Coverage mask upload failed!");
return;
}
// TODO: Keep using this texture until full and cache the results, then get a new one.
}
}
void RasterPathAtlas::onAddShape(const Shape& shape,
const Transform& transform,
const Rect& atlasBounds,
skvx::int2 deviceOffset,
const SkStrokeRec& strokeRec) {
// TODO: look up shape and use cached texture
// Need to push this up into addShape() somehow
// allocate pixmap if needed
if (!fPixels.addr()) {
const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(kAtlasDim, kAtlasDim);
if (!fPixels.tryAlloc(bmImageInfo)) {
return;
}
fPixels.erase(0);
}
// Rasterize path to backing pixmap
// TODO: render in a separate thread?
SkDrawBase draw;
draw.fBlitterChooser = SkA8Blitter_Choose;
draw.fDst = fPixels;
SkRasterClip rasterClip;
SkIRect iAtlasBounds = atlasBounds.asSkIRect();
rasterClip.setRect(iAtlasBounds);
draw.fRC = &rasterClip;
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // "Replace" mode
paint.setAntiAlias(true);
// SkPaint's color is unpremul so this will produce alpha in every channel.
paint.setColor(SK_ColorWHITE);
strokeRec.applyToPaint(&paint);
SkMatrix translatedMatrix = SkMatrix(transform);
// The atlas transform of the shape is the linear-components (scale, rotation, skew) of
// `localToDevice` translated by the top-left offset of `atlasBounds`, accounting for the 1
// pixel-wide border we added earlier, so that the shape is correctly centered.
translatedMatrix.postTranslate(atlasBounds.x() + 1 - deviceOffset.x(),
atlasBounds.y() + 1 - deviceOffset.y());
draw.fCTM = &translatedMatrix;
SkPath path = shape.asPath();
if (path.isInverseFillType()) {
// The shader will handle the inverse fill in this case
path.toggleInverseFillType();
}
draw.drawPathCoverage(path, paint);
// Add atlasBounds to dirtyRect for later upload
fDirtyRect.join(iAtlasBounds);
// TODO: cache shape data and texture used
}
void RasterPathAtlas::onReset() {
// clear backing data for next pass
fDirtyRect.setEmpty();
fPixels.erase(0);
}
PathAtlas::MaskFormat RasterPathAtlas::coverageMaskFormat(const Caps*) const {
return {kAlpha_8_SkColorType, /*requiresStorageUsage=*/false};
}
} // namespace skgpu::graphite