blob: b77557ae2ec17f76e135bfb8cb229ebf3f93d714 [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 "fuzz/Fuzz.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkFont.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkRefCnt.h"
#include "include/effects/SkColorMatrix.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Surface.h"
#include "modules/skcms/skcms.h"
#include "src/core/SkBlenderBase.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/FactoryFunctions.h"
#include "src/gpu/graphite/KeyContext.h"
#include "src/gpu/graphite/PaintOptionsPriv.h"
#include "src/gpu/graphite/PaintParams.h"
#include "src/gpu/graphite/PaintParamsKey.h"
#include "src/gpu/graphite/PipelineData.h"
#include "src/gpu/graphite/Precompile.h"
#include "src/gpu/graphite/PublicPrecompile.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/RuntimeEffectDictionary.h"
#include "tools/ToolUtils.h"
#include "tools/gpu/GrContextFactory.h"
#include "tools/graphite/ContextFactory.h"
using namespace skgpu::graphite;
namespace {
SkBlendMode random_blend_mode(Fuzz* fuzz) {
uint32_t temp;
fuzz->next(&temp);
return (SkBlendMode) (temp % kSkBlendModeCount);
}
SkColor random_opaque_skcolor(Fuzz* fuzz) {
SkColor color;
fuzz->next(&color);
return 0xff000000 | color;
}
SkColor4f random_color4f(Fuzz* fuzz) {
bool makeOpaque;
fuzz->next(&makeOpaque);
SkColor4f color;
fuzz->nextRange(&color.fR, 0, 1);
fuzz->nextRange(&color.fG, 0, 1);
fuzz->nextRange(&color.fB, 0, 1);
if (makeOpaque) {
color.fA = 1.0;
} else {
fuzz->nextRange(&color.fA, 0, 1);
}
return color;
}
SkPath make_path() {
SkPathBuilder path;
path.moveTo(0, 0);
path.lineTo(8, 2);
path.lineTo(16, 0);
path.lineTo(14, 8);
path.lineTo(16, 16);
path.lineTo(8, 14);
path.lineTo(0, 16);
path.lineTo(2, 8);
path.close();
return path.detach();
}
#ifdef SK_DEBUG
void dump(ShaderCodeDictionary* dict, UniquePaintParamsID id) {
dict->lookup(id).dump(dict);
}
#endif
//--------------------------------------------------------------------------------------------------
// color spaces
const skcms_TransferFunction& random_transfer_function(Fuzz* fuzz) {
static constexpr skcms_TransferFunction gTransferFunctions[] = {
SkNamedTransferFn::kSRGB,
SkNamedTransferFn::k2Dot2,
SkNamedTransferFn::kLinear,
SkNamedTransferFn::kRec2020,
SkNamedTransferFn::kPQ,
SkNamedTransferFn::kHLG,
};
uint32_t xferFunction;
fuzz->next(&xferFunction);
xferFunction %= std::size(gTransferFunctions);
return gTransferFunctions[xferFunction];
}
const skcms_Matrix3x3& random_gamut(Fuzz* fuzz) {
static constexpr skcms_Matrix3x3 gGamuts[] = {
SkNamedGamut::kSRGB,
SkNamedGamut::kAdobeRGB,
SkNamedGamut::kDisplayP3,
SkNamedGamut::kRec2020,
SkNamedGamut::kXYZ,
};
uint32_t gamut;
fuzz->next(&gamut);
gamut %= std::size(gGamuts);
return gGamuts[gamut];
}
enum class ColorSpaceType {
kNone,
kSRGB,
kSRGBLinear,
kRGB,
kLast = kRGB
};
static constexpr int kColorSpaceTypeCount = static_cast<int>(ColorSpaceType::kLast) + 1;
sk_sp<SkColorSpace> create_colorspace(Fuzz* fuzz, ColorSpaceType csType) {
switch (csType) {
case ColorSpaceType::kNone:
return nullptr;
case ColorSpaceType::kSRGB:
return SkColorSpace::MakeSRGB();
case ColorSpaceType::kSRGBLinear:
return SkColorSpace::MakeSRGBLinear();
case ColorSpaceType::kRGB:
return SkColorSpace::MakeRGB(random_transfer_function(fuzz), random_gamut(fuzz));
}
SkUNREACHABLE;
}
sk_sp<SkColorSpace> create_random_colorspace(Fuzz* fuzz) {
uint32_t temp;
fuzz->next(&temp);
ColorSpaceType csType = (ColorSpaceType) (temp % kColorSpaceTypeCount);
return create_colorspace(fuzz, csType);
}
//--------------------------------------------------------------------------------------------------
// color filters
enum class ColorFilterType {
kNone,
kBlend,
kMatrix,
kHSLAMatrix,
// TODO: add more color filters
kLast = kHSLAMatrix
};
static constexpr int kColorFilterTypeCount = static_cast<int>(ColorFilterType::kLast) + 1;
std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_blend_colorfilter(
Fuzz* fuzz) {
sk_sp<SkColorFilter> cf;
// SkColorFilters::Blend is clever and can weed out noop color filters. Loop until we get
// a valid color filter.
while (!cf && !fuzz->exhausted()) {
cf = SkColorFilters::Blend(random_color4f(fuzz),
create_random_colorspace(fuzz),
random_blend_mode(fuzz));
}
sk_sp<PrecompileColorFilter> o = cf ? PrecompileColorFilters::Blend() : nullptr;
return { cf, o };
}
std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_matrix_colorfilter() {
sk_sp<SkColorFilter> cf = SkColorFilters::Matrix(
SkColorMatrix::RGBtoYUV(SkYUVColorSpace::kJPEG_Full_SkYUVColorSpace));
sk_sp<PrecompileColorFilter> o = PrecompileColorFilters::Matrix();
return { cf, o };
}
std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_hsla_matrix_colorfilter() {
sk_sp<SkColorFilter> cf = SkColorFilters::HSLAMatrix(
SkColorMatrix::RGBtoYUV(SkYUVColorSpace::kJPEG_Full_SkYUVColorSpace));
sk_sp<PrecompileColorFilter> o = PrecompileColorFilters::HSLAMatrix();
return { cf, o };
}
std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_colorfilter(
Fuzz* fuzz,
ColorFilterType type,
int depth) {
if (depth <= 0) {
return {};
}
switch (type) {
case ColorFilterType::kNone:
return { nullptr, nullptr };
case ColorFilterType::kBlend:
return create_blend_colorfilter(fuzz);
case ColorFilterType::kMatrix:
return create_matrix_colorfilter();
case ColorFilterType::kHSLAMatrix:
return create_hsla_matrix_colorfilter();
}
SkUNREACHABLE;
}
std::pair<sk_sp<SkColorFilter>, sk_sp<PrecompileColorFilter>> create_random_colorfilter(
Fuzz* fuzz,
int depth) {
uint32_t temp;
fuzz->next(&temp);
ColorFilterType cf = (ColorFilterType) (temp % kColorFilterTypeCount);
return create_colorfilter(fuzz, cf, depth);
}
//--------------------------------------------------------------------------------------------------
std::pair<SkPaint, PaintOptions> create_random_paint(Fuzz* fuzz, int depth) {
if (depth <= 0) {
return {};
}
SkPaint paint;
paint.setColor(random_opaque_skcolor(fuzz));
PaintOptions paintOptions;
{
auto [cf, o] = create_random_colorfilter(fuzz, depth - 1);
SkASSERT_RELEASE(!cf == !o);
if (cf) {
paint.setColorFilter(std::move(cf));
paintOptions.setColorFilters({o});
}
}
return { paint, paintOptions };
}
//--------------------------------------------------------------------------------------------------
void check_draw(Context* context,
Recorder* recorder,
const SkPaint& paint,
DrawTypeFlags dt,
const SkPath& path) {
int before = context->priv().globalCache()->numGraphicsPipelines();
{
// TODO: vary the colorType of the target surface too
SkImageInfo ii = SkImageInfo::Make(16, 16,
kRGBA_8888_SkColorType,
kPremul_SkAlphaType);
sk_sp<SkSurface> surf = SkSurfaces::RenderTarget(recorder, ii);
SkCanvas* canvas = surf->getCanvas();
switch (dt) {
case DrawTypeFlags::kShape:
canvas->drawRect(SkRect::MakeWH(16, 16), paint);
canvas->drawPath(path, paint);
break;
default:
SkASSERT_RELEASE(false);
break;
}
std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
context->insertRecording({ recording.get() });
context->submit(SyncToCpu::kYes);
}
int after = context->priv().globalCache()->numGraphicsPipelines();
// Actually using the SkPaint with the specified type of draw shouldn't have caused
// any additional compilation
SkASSERT_RELEASE(before == after);
}
void fuzz_graphite(Fuzz* fuzz, Context* context, int depth = 9) {
auto recorder = context->makeRecorder();
ShaderCodeDictionary* dict = context->priv().shaderCodeDictionary();
SkColorInfo ci = SkColorInfo(kRGBA_8888_SkColorType, kPremul_SkAlphaType,
SkColorSpace::MakeSRGB());
std::unique_ptr<RuntimeEffectDictionary> rtDict = std::make_unique<RuntimeEffectDictionary>();
KeyContext precompileKeyContext(recorder->priv().caps(),
dict,
rtDict.get(),
ci,
/* dstTexture= */ nullptr,
/* dstOffset= */ {0, 0});
auto dstTexInfo = recorder->priv().caps()->getDefaultSampledTextureInfo(kRGBA_8888_SkColorType,
skgpu::Mipmapped::kNo,
skgpu::Protected::kNo,
skgpu::Renderable::kNo);
// Use Budgeted::kYes to avoid immediately instantiating the TextureProxy. This test doesn't
// require full resources.
sk_sp<TextureProxy> fakeDstTexture = TextureProxy::Make(recorder->priv().caps(),
recorder->priv().resourceProvider(),
SkISize::Make(1, 1),
dstTexInfo,
"FuzzPrecompileFakeDstTexture",
skgpu::Budgeted::kYes);
constexpr SkIPoint fakeDstOffset = SkIPoint::Make(0, 0);
DrawTypeFlags kDrawType = DrawTypeFlags::kShape;
SkPath path = make_path();
Layout layout = context->backend() == skgpu::BackendApi::kMetal ? Layout::kMetal
: Layout::kStd140;
PaintParamsKeyBuilder builder(dict);
PipelineDataGatherer gatherer(layout);
auto [paint, paintOptions] = create_random_paint(fuzz, depth);
constexpr Coverage coverageOptions[3] = {
Coverage::kNone, Coverage::kSingleChannel, Coverage::kLCD};
uint32_t temp;
fuzz->next(&temp);
Coverage coverage = coverageOptions[temp % 3];
DstReadRequirement dstReadReq = DstReadRequirement::kNone;
const SkBlenderBase* blender = as_BB(paint.getBlender());
if (blender) {
dstReadReq = GetDstReadRequirement(recorder->priv().caps(),
blender->asBlendMode(),
coverage);
}
bool needsDstSample = dstReadReq == DstReadRequirement::kTextureCopy ||
dstReadReq == DstReadRequirement::kTextureSample;
sk_sp<TextureProxy> curDst = needsDstSample ? fakeDstTexture : nullptr;
auto [paintID, uData, tData] = ExtractPaintData(
recorder.get(), &gatherer, &builder, layout, {},
PaintParams(paint,
/* primitiveBlender= */ nullptr,
/* clipShader= */nullptr,
dstReadReq,
/* skipColorXform= */ false),
curDst, fakeDstOffset, ci);
std::vector<UniquePaintParamsID> precompileIDs;
paintOptions.priv().buildCombinations(precompileKeyContext,
&gatherer,
DrawTypeFlags::kNone,
/* withPrimitiveBlender= */ false,
coverage,
[&](UniquePaintParamsID id,
DrawTypeFlags,
bool /* withPrimitiveBlender */,
Coverage) {
precompileIDs.push_back(id);
});
// The specific key generated by ExtractPaintData should be one of the
// combinations generated by the combination system.
auto result = std::find(precompileIDs.begin(), precompileIDs.end(), paintID);
#ifdef SK_DEBUG
if (result == precompileIDs.end()) {
SkDebugf("From paint: ");
dump(dict, paintID);
SkDebugf("From combination builder:");
for (auto iter : precompileIDs) {
dump(dict, iter);
}
}
#endif
SkASSERT_RELEASE(result != precompileIDs.end());
{
context->priv().globalCache()->resetGraphicsPipelines();
int before = context->priv().globalCache()->numGraphicsPipelines();
Precompile(context, paintOptions, kDrawType);
int after = context->priv().globalCache()->numGraphicsPipelines();
SkASSERT_RELEASE(before == 0);
SkASSERT_RELEASE(after > before);
check_draw(context, recorder.get(), paint, kDrawType, path);
}
}
} // anonymous namespace
DEF_FUZZ(Precompile, fuzz) {
skiatest::graphite::ContextFactory factory;
skgpu::ContextType contextType;
#if defined(SK_METAL)
contextType = skgpu::ContextType::kMetal;
#elif defined(SK_VULKAN)
contextType = skgpu::ContextType::kVulkan;
#else
contextType = skgpu::ContextType::kMock;
#endif
skiatest::graphite::ContextInfo ctxInfo = factory.getContextInfo(contextType);
skgpu::graphite::Context* context = ctxInfo.fContext;
if (!context) {
return;
}
fuzz_graphite(fuzz, context);
}