[graphite] Add Combination to GraphicsPipelineDesc

Change-Id: I7fa9b4da48f993143ade3b8030e2c67831cf0ffd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/475257
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2021-11-24 15:44:19 -05:00 committed by SkCQ
parent a0ad6db141
commit 0814e7fb6a
13 changed files with 130 additions and 272 deletions

View File

@ -11,9 +11,10 @@
#include "experimental/graphite/src/CommandBuffer.h"
#include "experimental/graphite/src/ContextUtils.h"
#include "experimental/graphite/src/Gpu.h"
#include "experimental/graphite/src/ProgramCache.h"
#include "experimental/graphite/src/GraphicsPipelineDesc.h"
#include "experimental/graphite/src/Recorder.h"
#include "experimental/graphite/src/Recording.h"
#include "experimental/graphite/src/Renderer.h"
#ifdef SK_METAL
#include "experimental/graphite/src/mtl/MtlTrampoline.h"
@ -55,21 +56,23 @@ void Context::submit(SyncToCpu syncToCpu) {
}
void Context::preCompile(const PaintCombo& paintCombo) {
ProgramCache cache;
for (auto bm: paintCombo.fBlendModes) {
for (auto& shaderCombo: paintCombo.fShaders) {
for (auto shaderType: shaderCombo.fTypes) {
for (auto tm: shaderCombo.fTileModes) {
Combination c {shaderType, tm, bm};
sk_sp<ProgramCache::ProgramInfo> pi = cache.findOrCreateProgram(c);
// TODO: this should be getSkSL
// TODO: it should also return the uniform information
std::string msl = pi->getMSL();
// TODO: compile the MSL and store the result back into the ProgramInfo
// To do this we will need the path rendering options from Chris and
// a stock set of RenderPasses.
GraphicsPipelineDesc desc;
for (const Renderer* r : {&Renderer::StencilAndFillPath()}) {
for (auto&& s : r->steps()) {
desc.setProgram(s, c);
// TODO: Combine with renderpass description set to generate full
// GraphicsPipeline and MSL program. Cache that compiled pipeline on
// the resource provider in a map from desc -> pipeline so that any
// later desc created from equivalent RenderStep + Combination get it.
}
}
}
}
}

View File

@ -40,6 +40,30 @@ static constexpr Uniform kSolidUniforms[kNumSolidUniforms] {
{"color", SLType::kFloat4 }
};
static const char* kLinearGradientMSL =
// TODO: This should use local coords
"float2 pos = interpolated.position.xy;\n"
"float2 delta = uniforms.point1 - uniforms.point0;\n"
"float2 pt = pos - uniforms.point0;\n"
"float t = dot(pt, delta) / dot(delta, delta);\n"
"float4 result = uniforms.colors[0];\n"
"result = mix(result, uniforms.colors[1],\n"
" clamp((t-uniforms.offsets[0])/(uniforms.offsets[1]-uniforms.offsets[0]),\n"
" 0, 1));\n"
"result = mix(result, uniforms.colors[2],\n"
" clamp((t-uniforms.offsets[1])/(uniforms.offsets[2]-uniforms.offsets[1]),\n"
" 0, 1));\n"
"result = mix(result, uniforms.colors[3],\n"
" clamp((t-uniforms.offsets[2])/(uniforms.offsets[3]-uniforms.offsets[2]),\n"
" 0, 1));\n"
"out.color = result;\n";
static const char* kSolidColorMSL = "out.color = float4(uniforms.color);\n";
// TODO: kNone is for depth-only draws, so should actually have a fragment output type
// that only defines a [[depth]] attribute but no color calculation.
static const char* kNoneMSL = "out.color float4(0.0, 0.0, 1.0, 1.0);\n";
sk_sp<UniformData> make_gradient_uniform_data_common(void* srcs[kNumGradientUniforms]) {
UniformManager mgr(Layout::kMetal);
@ -268,60 +292,33 @@ std::tuple<Combination, sk_sp<UniformData>> ExtractCombo(const PaintParams& p) {
return { result, std::move(uniforms) };
}
namespace {
// TODO: use a SkSpan for the parameters
std::string emit_MSL_uniform_struct(const Uniform *uniforms, int numUniforms) {
std::string result;
result.append("struct FragmentUniforms {\n");
for (int i = 0; i < numUniforms; ++i) {
// TODO: this is sufficient for the sprint but should be changed to use SkSL's
// machinery
switch (uniforms[i].type()) {
case SLType::kFloat4:
result.append("vector_float4");
break;
case SLType::kFloat2:
result.append("vector_float2");
break;
case SLType::kFloat:
result.append("float");
break;
case SLType::kHalf4:
result.append("vector_half4");
break;
default:
SkASSERT(0);
}
result.append(" ");
result.append(uniforms[i].name());
if (uniforms[i].count()) {
result.append("[");
result.append(std::to_string(uniforms[i].count()));
result.append("]");
}
result.append(";\n");
}
result.append("};\n");
return result;
}
} // anonymous namespace
std::string GetMSLUniformStruct(ShaderCombo::ShaderType shaderType) {
SkSpan<const Uniform> GetUniforms(ShaderCombo::ShaderType shaderType) {
switch (shaderType) {
case ShaderCombo::ShaderType::kLinearGradient:
case ShaderCombo::ShaderType::kRadialGradient:
case ShaderCombo::ShaderType::kSweepGradient:
case ShaderCombo::ShaderType::kConicalGradient:
return emit_MSL_uniform_struct(kGradientUniforms, kNumGradientUniforms);
return SkMakeSpan(kGradientUniforms, kNumGradientUniforms);
case ShaderCombo::ShaderType::kNone:
return "";
return {nullptr, 0};
case ShaderCombo::ShaderType::kSolidColor:
default:
return emit_MSL_uniform_struct(kSolidUniforms, kNumSolidUniforms);
return SkMakeSpan(kSolidUniforms, kNumSolidUniforms);
}
}
const char* GetShaderMSL(ShaderCombo::ShaderType shaderType) {
switch (shaderType) {
case ShaderCombo::ShaderType::kLinearGradient:
return kLinearGradientMSL;
case ShaderCombo::ShaderType::kNone:
return kNoneMSL;
case ShaderCombo::ShaderType::kRadialGradient:
case ShaderCombo::ShaderType::kSweepGradient:
case ShaderCombo::ShaderType::kConicalGradient:
case ShaderCombo::ShaderType::kSolidColor:
default:
return kSolidColorMSL;
}
}

View File

@ -11,6 +11,7 @@
#include "experimental/graphite/include/Context.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTileMode.h"
namespace skgpu {
@ -86,7 +87,13 @@ private:
};
std::tuple<Combination, sk_sp<UniformData>> ExtractCombo(const PaintParams&);
std::string GetMSLUniformStruct(ShaderCombo::ShaderType);
SkSpan<const Uniform> GetUniforms(ShaderCombo::ShaderType);
// TODO: Temporary way to get at MSL snippet for handling the given shader type, which will be
// embedded in the fragment function's body. It has access to the vertex output via a "interpolated"
// variable, and must have a statement that writes to a float4 "out.color". Its uniforms (as defined
// by GetUniforms(type)) are available as a variable named "uniforms".
const char* GetShaderMSL(ShaderCombo::ShaderType);
} // namespace skgpu

View File

@ -15,7 +15,7 @@
#include "experimental/graphite/src/DrawContext.h"
#include "experimental/graphite/src/DrawList.h"
#include "experimental/graphite/src/DrawWriter.h"
#include "experimental/graphite/src/ProgramCache.h"
#include "experimental/graphite/src/GraphicsPipelineDesc.h"
#include "experimental/graphite/src/Recorder.h"
#include "experimental/graphite/src/Renderer.h"
#include "experimental/graphite/src/TextureProxy.h"
@ -28,20 +28,7 @@
#include "src/gpu/BufferWriter.h"
#include <algorithm>
namespace {
// Retrieve the program ID and uniformData ID
std::tuple<uint32_t, uint32_t> get_ids_from_paint(skgpu::Recorder* recorder,
skgpu::PaintParams params) {
// TODO: perhaps just return the ids here rather than the sk_sps?
auto [ combo, uniformData] = ExtractCombo(params);
auto programInfo = recorder->programCache()->findOrCreateProgram(combo);
auto uniformID = recorder->uniformCache()->insert(std::move(uniformData));
return { programInfo->id(), uniformID };
}
} // anonymous namespace
#include <unordered_map>
namespace skgpu {
@ -226,19 +213,24 @@ std::unique_ptr<DrawPass> DrawPass::Make(Recorder* recorder,
// If we have two different descriptors, such that the uniforms from the PaintParams can be
// bound independently of those used by the rest of the RenderStep, then we can upload now
// and remember the location for re-use on any RenderStep that does shading.
uint32_t programID = ProgramCache::kInvalidProgramID;
uint32_t shadingUniformID = UniformCache::kInvalidUniformID;
Combination shader;
sk_sp<UniformData> shadingUniforms = nullptr;
uint32_t shadingIndex = UniformCache::kInvalidUniformID;
if (draw.fPaintParams.has_value()) {
std::tie(programID, shadingUniformID) = get_ids_from_paint(recorder,
draw.fPaintParams.value());
}
std::tie(shader, shadingUniforms) = ExtractCombo(draw.fPaintParams.value());
shadingIndex = recorder->uniformCache()->insert(shadingUniforms);
} // else depth-only
for (int stepIndex = 0; stepIndex < draw.fRenderer.numRenderSteps(); ++stepIndex) {
const RenderStep* const step = draw.fRenderer.steps()[stepIndex];
const bool performsShading = draw.fPaintParams.has_value() && step->performsShading();
// TODO ask step to generate a pipeline description based on the above shading code, and
// have pipelineIndex point to that description in the accumulated list of descs
uint32_t pipelineIndex = 0;
Combination stepShader;
uint32_t stepShadingIndex = UniformCache::kInvalidUniformID;
if (performsShading) {
stepShader = shader;
stepShadingIndex = shadingIndex;
} // else depth-only draw or stencil-only step of renderer so no shading is needed
uint32_t geometryIndex = UniformCache::kInvalidUniformID;
if (step->numUniforms() > 0) {
@ -254,19 +246,13 @@ std::unique_ptr<DrawPass> DrawPass::Make(Recorder* recorder,
}
}
uint32_t shadingIndex = UniformCache::kInvalidUniformID;
GraphicsPipelineDesc desc;
desc.setProgram(step, stepShader);
uint32_t pipelineIndex = 0;
const bool performsShading = draw.fPaintParams.has_value() && step->performsShading();
if (performsShading) {
// TODO: we need to combine the 'programID' with the RenderPass info and the
// geometric rendering method to get the true 'pipelineIndex'
pipelineIndex = programID;
shadingIndex = shadingUniformID;
} else {
// TODO: fill in 'pipelineIndex' for Chris' stencil/depth draws
}
// TODO: Have a map from descriptions to uint32_ts for the SortKey
keys.push_back({&draw, stepIndex, pipelineIndex, geometryIndex, shadingIndex});
keys.push_back({&draw, stepIndex, pipelineIndex, geometryIndex, stepShadingIndex});
}
passBounds.join(draw.fClip.drawBounds());
@ -287,8 +273,9 @@ std::unique_ptr<DrawPass> DrawPass::Make(Recorder* recorder,
Drawer drawer;
DrawWriter drawWriter(&drawer, bufferMgr);
// Used to track when a new pipeline or dynamic state needs recording between draw steps
uint32_t lastPipeline = 0;
// Used to track when a new pipeline or dynamic state needs recording between draw steps.
// Setting to # render steps ensures the very first time through the loop will bind a pipeline.
uint32_t lastPipeline = draws->renderStepCount();
uint32_t lastShadingUniforms = UniformCache::kInvalidUniformID;
uint32_t lastGeometryUniforms = UniformCache::kInvalidUniformID;
SkIRect lastScissor = SkIRect::MakeSize(target->dimensions());

View File

@ -11,9 +11,11 @@
#include "include/core/SkTypes.h"
#include "experimental/graphite/src/Attribute.h"
#include "experimental/graphite/src/ContextUtils.h"
#include "experimental/graphite/src/DrawTypes.h"
#include "include/private/SkTArray.h"
#include <array>
namespace skgpu {
class RenderStep;
@ -45,32 +47,32 @@ public:
return !(*this == other);
}
// Describes the geometric portion of the pipeline's program and the pipeline's fixed state
// (except for renderpass-level state that will never change between draws).
const RenderStep* renderStep() const { return fRenderStep; }
void setRenderStep(const RenderStep* step) {
// Key describing the color shading tree of the pipeline's program
Combination shaderCombo() const { return fCombination; }
void setProgram(const RenderStep* step, const Combination& shaderCombo) {
SkASSERT(step);
fRenderStep = step;
static constexpr int kWords = sizeof(uintptr_t) / sizeof(uint32_t);
static_assert(sizeof(uintptr_t) % sizeof(uint32_t) == 0);
if (fKey.count() < kWords) {
fKey.push_back_n(kWords - fKey.count());
}
fCombination = shaderCombo;
uintptr_t addr = reinterpret_cast<uintptr_t>(fRenderStep);
memcpy(fKey.data(), &addr, sizeof(uintptr_t));
fKey[kWords - 1] = shaderCombo.key();
}
private:
// Estimate of max expected key size
// TODO: flesh this out
inline static constexpr int kPreAllocSize = 1;
// The key is the RenderStep address and the uint32_t key from Combination
static constexpr int kWords = sizeof(uintptr_t) / sizeof(uint32_t) + 1;
static_assert(sizeof(uintptr_t) % sizeof(uint32_t) == 0);
// TODO: I wonder if we could expose the "key" as just a char[] union over the renderstep and
// paint combination? That would avoid extra size, but definitely locks GraphicsPipelineDesc
// keys to the current process, which is probably okay since we can have something a with a more
// stable hash used for the pre-compilation combos.
SkSTArray<kPreAllocSize, uint32_t, true> fKey;
std::array<uint32_t, kWords> fKey;
// Each RenderStep defines a fixed set of attributes and rasterization state, as well as the
// shader fragments that control the geometry and coverage calculations. The RenderStep's shader
@ -78,6 +80,12 @@ private:
// RenderStep is fixed, its pointer can be used as a proxy for everything that it specifies in
// the GraphicsPipeline.
const RenderStep* fRenderStep = nullptr;
// TODO: Right now the Combination is roughly the equivalent of the PaintBlob description, so
// eventually it won't be a fixed size, as it can eventually represent arbitrary shader trees.
// However, in that world, each PaintBlob structure will have a unique ID and a map from ID to
// blob, so the GraphicsPipelineDesc can be reduced to just storing RenderStep + unique ID int.
Combination fCombination;
};
} // namespace skgpu

View File

@ -1,67 +0,0 @@
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef skgpu_ProgramCache_DEFINED
#define skgpu_ProgramCache_DEFINED
#include <unordered_map>
#include <string>
#include <vector>
#include "experimental/graphite/src/ContextUtils.h"
#include "include/core/SkRefCnt.h"
namespace skgpu {
class ProgramCache {
public:
ProgramCache();
static constexpr uint32_t kInvalidProgramID = 0;
// TODO: this is a bit underspecified. It still needs the rendering technique info.
// Additionally, it still needs an entry point to generate the text of the program.
class ProgramInfo : public SkRefCnt {
public:
ProgramInfo(uint32_t uniqueID, Combination c);
~ProgramInfo() override;
uint32_t id() const { return fID; }
Combination combo() const { return fCombination; }
std::string getMSL() const;
private:
const uint32_t fID;
const Combination fCombination;
// TODO: store the rendering technique info from Chris here
};
// TODO: we need the rendering technique info from Chris for this look up
sk_sp<ProgramInfo> findOrCreateProgram(Combination);
sk_sp<ProgramInfo> lookup(uint32_t uniqueID);
// The number of unique programs in the cache
size_t count() const {
SkASSERT(fProgramHash.size()+1 == fProgramVector.size());
return fProgramHash.size();
}
private:
struct Hash {
size_t operator()(Combination) const;
};
std::unordered_map<Combination, sk_sp<ProgramInfo>, Hash> fProgramHash;
std::vector<sk_sp<ProgramInfo>> fProgramVector;
// The ProgramInfo's unique ID is only unique w/in a Recorder _not_ globally
uint32_t fNextUniqueID = 1;
};
} // namespace skgpu
#endif // skgpu_ProgramCache_DEFINED

View File

@ -12,7 +12,6 @@
#include "experimental/graphite/src/ContextPriv.h"
#include "experimental/graphite/src/DrawBufferManager.h"
#include "experimental/graphite/src/Gpu.h"
#include "experimental/graphite/src/ProgramCache.h"
#include "experimental/graphite/src/Recording.h"
#include "experimental/graphite/src/ResourceProvider.h"
#include "experimental/graphite/src/UniformCache.h"
@ -21,7 +20,6 @@ namespace skgpu {
Recorder::Recorder(sk_sp<Context> context)
: fContext(std::move(context))
, fProgramCache(new ProgramCache)
, fUniformCache(new UniformCache)
// TODO: Is '4' the correct initial alignment?
, fDrawBufferManager(new DrawBufferManager(fContext->priv().gpu()->resourceProvider(), 4)) {
@ -33,10 +31,6 @@ Context* Recorder::context() const {
return fContext.get();
}
ProgramCache* Recorder::programCache() {
return fProgramCache.get();
}
UniformCache* Recorder::uniformCache() {
return fUniformCache.get();
}

View File

@ -15,7 +15,6 @@ namespace skgpu {
class Context;
class DrawBufferManager;
class ProgramCache;
class Recording;
class UniformCache;
@ -27,7 +26,6 @@ public:
void add(sk_sp<Task>);
Context* context() const;
ProgramCache* programCache();
UniformCache* uniformCache();
DrawBufferManager* drawBufferManager();
@ -37,7 +35,6 @@ protected:
private:
sk_sp<Context> fContext;
TaskGraph fGraph;
std::unique_ptr<ProgramCache> fProgramCache;
std::unique_ptr<UniformCache> fUniformCache;
std::unique_ptr<DrawBufferManager> fDrawBufferManager;
};

View File

@ -121,6 +121,9 @@ SkSL::String get_msl(const GraphicsPipelineDesc& desc) {
"typedef struct {\n"
" float4 position [[position]];\n"
"} VertexOutput;\n"
"typedef struct {\n"
" float4 color [[color(0)]];\n"
"} FragmentOutput;\n"
"\n";
// Typedefs needed by RenderStep
@ -151,14 +154,16 @@ SkSL::String get_msl(const GraphicsPipelineDesc& desc) {
"}\n";
// Typedefs needed for painting
// TODO: Right now hardcoded float4 color uniform but will come from Combination once that is
// stored on the GraphicsPipelineDesc.
msl += "struct PaintUniforms {\n"
" float4 color;\n"
"};\n";
msl += "fragment float4 fragmentMain(VertexOutput in [[stage_in]],\n"
" constant PaintUniforms& uniforms [[buffer(1)]]) {\n"
" return uniforms.color;\n"
auto paintUniforms = GetUniforms(desc.shaderCombo().fShaderType);
if (!paintUniforms.empty()) {
msl += emit_MSL_uniform_struct("FragUniforms", paintUniforms);
}
msl += "fragment FragmentOutput fragmentMain(VertexOutput interpolated [[stage_in]],\n"
" constant FragUniforms& uniforms [[buffer(1)]]) {\n"
" FragmentOutput out;\n";
msl += GetShaderMSL(desc.shaderCombo().fShaderType);
msl += " return out;\n"
"}\n";
return msl;

View File

@ -56,8 +56,6 @@ skia_graphite_sources = [
"$_src/GraphicsPipelineDesc.h",
"$_src/Image_Graphite.cpp",
"$_src/Image_Graphite.h",
"$_src/ProgramCache.cpp",
"$_src/ProgramCache.h",
"$_src/Recorder.cpp",
"$_src/Recorder.h",
"$_src/Recording.cpp",

View File

@ -333,7 +333,6 @@ graphite_tests_sources = [
"$_tests/graphite/CommandBufferTest.cpp",
"$_tests/graphite/IntersectionTreeTest.cpp",
"$_tests/graphite/MaskTest.cpp",
"$_tests/graphite/ProgramCacheTest.cpp",
"$_tests/graphite/RectTest.cpp",
"$_tests/graphite/ShapeTest.cpp",
"$_tests/graphite/TransformTest.cpp",

View File

@ -257,6 +257,7 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
DrawBufferManager bufferMgr(gpu->resourceProvider(), 4);
commandBuffer->beginRenderPass(renderPassDesc);
DrawWriter drawWriter(commandBuffer->asDrawDispatcher(), &bufferMgr);
struct RectAndColor {
@ -265,8 +266,9 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
};
auto draw = [&](const RenderStep* step, std::vector<RectAndColor> draws) {
Combination shader{ShaderCombo::ShaderType::kSolidColor};
GraphicsPipelineDesc pipelineDesc;
pipelineDesc.setRenderStep(step);
pipelineDesc.setProgram(step, shader);
drawWriter.newPipelineState(step->primitiveType(),
step->vertexStride(),
step->instanceStride());
@ -283,17 +285,16 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
bufferMgr.getUniformWriter(renderStepUniforms->dataSize());
writer.write(renderStepUniforms->data(), renderStepUniforms->dataSize());
commandBuffer->bindUniformBuffer(UniformSlot::kRenderStep,
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
}
// TODO: Hard-coded solid color uniform for the fragment shader is always combined
// with the RenderStep's vertex shader.
// TODO: Rely on uniform writer and GetUniforms(kSolidColor).
auto [writer, bindInfo] = bufferMgr.getUniformWriter(sizeof(SkColor4f));
writer.write(&d.fColor, sizeof(SkColor4f));
commandBuffer->bindUniformBuffer(UniformSlot::kPaint,
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
sk_ref_sp(bindInfo.fBuffer),
bindInfo.fOffset);
step->writeVertices(&drawWriter, shape);
}
@ -309,8 +310,8 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(CommandBufferTest, reporter, context) {
draw(TriangleRectDraw::Singleton(), {{{-.5f, -.5f, .5f, .5f}, SkColors::kMagenta}});
// Draw green and cyan rects using instance buffer
draw(InstanceRectDraw::Singleton(), { {{0.4f, -0.4f, 0.4f, 0.4f}, SkColors::kGreen},
{{0.f, 0.f, 0.25f, 0.25f}, SkColors::kCyan} });
draw(InstanceRectDraw::Singleton(), { {{-0.4f, -0.4f, 0.0f, 0.0f}, SkColors::kGreen},
{{0.f, 0.f, 0.25f, 0.25f}, SkColors::kCyan} });
drawWriter.flush();
bufferMgr.transferToCommandBuffer(commandBuffer.get());

View File

@ -1,71 +0,0 @@
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tests/Test.h"
#include "experimental/graphite/include/Context.h"
#include "experimental/graphite/src/ProgramCache.h"
#include "experimental/graphite/src/Recorder.h"
using namespace skgpu;
DEF_GRAPHITE_TEST_FOR_CONTEXTS(ProgramCacheTest, reporter, context) {
Recorder recorder(sk_ref_sp(context));
auto cache = recorder.programCache();
REPORTER_ASSERT(reporter, cache->count() == 0);
// Add an initial unique program
sk_sp<ProgramCache::ProgramInfo> pi1;
{
Combination c1 { ShaderCombo::ShaderType::kSolidColor,
SkTileMode::kRepeat,
SkBlendMode::kSrc };
pi1 = cache->findOrCreateProgram(c1);
REPORTER_ASSERT(reporter, pi1->id() != ProgramCache::kInvalidProgramID);
REPORTER_ASSERT(reporter, pi1->combo() == c1);
sk_sp<ProgramCache::ProgramInfo> lookup = cache->lookup(pi1->id());
REPORTER_ASSERT(reporter, lookup->id() == pi1->id());
REPORTER_ASSERT(reporter, cache->count() == 1);
}
// Try to add a duplicate program
{
Combination c2 { ShaderCombo::ShaderType::kSolidColor,
SkTileMode::kRepeat,
SkBlendMode::kSrc };
sk_sp<ProgramCache::ProgramInfo> pi2 = cache->findOrCreateProgram(c2);
REPORTER_ASSERT(reporter, pi2->id() != ProgramCache::kInvalidProgramID);
REPORTER_ASSERT(reporter, pi2->id() == pi1->id());
REPORTER_ASSERT(reporter, pi2->combo() == c2);
sk_sp<ProgramCache::ProgramInfo> lookup = cache->lookup(pi2->id());
REPORTER_ASSERT(reporter, lookup->id() == pi2->id());
REPORTER_ASSERT(reporter, cache->count() == 1);
}
// Add a second unique program
{
Combination c3 { ShaderCombo::ShaderType::kLinearGradient,
SkTileMode::kRepeat,
SkBlendMode::kSrc };
sk_sp<ProgramCache::ProgramInfo> pi3 = cache->findOrCreateProgram(c3);
REPORTER_ASSERT(reporter, pi3->id() != ProgramCache::kInvalidProgramID);
REPORTER_ASSERT(reporter, pi3->id() != pi1->id());
REPORTER_ASSERT(reporter, pi3->combo() == c3);
sk_sp<ProgramCache::ProgramInfo> lookup = cache->lookup(pi3->id());
REPORTER_ASSERT(reporter, lookup->id() == pi3->id());
REPORTER_ASSERT(reporter, cache->count() == 2);
}
// TODO(robertphillips): expand this test to exercise more program variations
}