Add GrMtlPipelineStateCache.

Allows us to cache MTLRenderPipelines rather than regenerating them
for every use.

Bug: skia:
Change-Id: I3357d3bc6d644074bd9d544a8d5205af56d918e5
Reviewed-on: https://skia-review.googlesource.com/c/195127
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
This commit is contained in:
Jim Van Verth 2019-02-28 17:38:35 -05:00 committed by Skia Commit-Bot
parent a8c728f080
commit 1223e7fc0a
9 changed files with 297 additions and 66 deletions

View File

@ -106,3 +106,20 @@ GrPipeline::GrPipeline(GrScissorTest scissorTest, SkBlendMode blendmode)
fFlags |= kScissorEnabled_Flag;
}
}
uint32_t GrPipeline::getBlendInfoKey() const {
GrXferProcessor::BlendInfo blendInfo;
this->getXferProcessor().getBlendInfo(&blendInfo);
static const uint32_t kBlendWriteShift = 1;
static const uint32_t kBlendCoeffShift = 5;
GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
uint32_t key = blendInfo.fWriteColor;
key |= (blendInfo.fSrcBlend << kBlendWriteShift);
key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
return key;
}

View File

@ -195,6 +195,9 @@ public:
return SkString("No pipeline flags\n");
}
// Used by Vulkan and Metal to cache their respective pipeline objects
uint32_t getBlendInfoKey() const;
private:
void markAsBad() { fFlags |= kIsBad_Flag; }

View File

@ -73,8 +73,7 @@ private:
const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,
const GrPipeline::FixedDynamicState* fixedDynamicState,
const GrMesh meshes[],
int meshCount);
GrPrimitiveType primType);
void onDraw(const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,

View File

@ -108,21 +108,8 @@ GrMtlPipelineState* GrMtlGpuRTCommandBuffer::prepareDrawState(
const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,
const GrPipeline::FixedDynamicState* fixedDynamicState,
const GrMesh meshes[],
int meshCount) {
GrPrimitiveType primType) {
// TODO: resolve textures and regenerate mipmaps as needed
bool hasPoints = false;
for (int i = 0; i < meshCount; ++i) {
if (meshes[i].primitiveType() == GrPrimitiveType::kPoints) {
hasPoints = true;
break;
}
}
GrProgramDesc desc;
if (!GrProgramDesc::Build(&desc, fRenderTarget->config(), primProc, hasPoints,
pipeline, fGpu)) {
return nullptr;
}
const GrTextureProxy* const* primProcProxies = nullptr;
if (fixedDynamicState) {
@ -130,17 +117,19 @@ GrMtlPipelineState* GrMtlGpuRTCommandBuffer::prepareDrawState(
}
SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
// TODO: use resource provider for pipeline
GrMtlPipelineState* pipelineState =
GrMtlPipelineStateBuilder::CreatePipelineState(fRenderTarget, fOrigin, primProc,
primProcProxies, pipeline,
&desc, fGpu);
fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, fOrigin,
pipeline,
primProc,
primProcProxies,
primType);
if (!pipelineState) {
return nullptr;
}
// We cannot have an active encoder when we set the pipeline data since it requires its own
// command encoder.
SkASSERT(fActiveRenderCmdEncoder == nil);
// TODO: this doesn't appear to be true -- we only use a command encoder here
// SkASSERT(fActiveRenderCmdEncoder == nil);
pipelineState->setData(fRenderTarget, fOrigin, primProc, pipeline, primProcProxies);
return pipelineState;
@ -160,15 +149,16 @@ void GrMtlGpuRTCommandBuffer::onDraw(const GrPrimitiveProcessor& primProc,
return; // TODO: ScissorRects are not supported.
}
std::unique_ptr<GrMtlPipelineState> pipelineState(
this->prepareDrawState(primProc, pipeline, fixedDynamicState, meshes, meshCount));
GrPrimitiveType primitiveType = meshes[0].primitiveType();
GrMtlPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline,
fixedDynamicState, primitiveType);
if (!pipelineState) {
return;
}
this->internalBegin();
[fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
[fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
pipelineState->bind(fActiveRenderCmdEncoder);
pipelineState->setBlendConstants(fActiveRenderCmdEncoder, fRenderTarget->config(),
pipeline.getXferProcessor());
@ -176,9 +166,26 @@ void GrMtlGpuRTCommandBuffer::onDraw(const GrPrimitiveProcessor& primProc,
for (int i = 0; i < meshCount; ++i) {
const GrMesh& mesh = meshes[i];
SkASSERT(fActiveRenderCmdEncoder);
SkASSERT(nil != fActiveRenderCmdEncoder);
if (mesh.primitiveType() != primitiveType) {
SkDEBUGCODE(pipelineState = nullptr);
primitiveType = mesh.primitiveType();
pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
primitiveType);
if (!pipelineState) {
return;
}
[fActiveRenderCmdEncoder setRenderPipelineState: pipelineState->mtlPipelineState()];
pipelineState->bind(fActiveRenderCmdEncoder);
pipelineState->setBlendConstants(fActiveRenderCmdEncoder, fRenderTarget->config(),
pipeline.getXferProcessor());
pipelineState->setDepthStencilState(fActiveRenderCmdEncoder);
}
mesh.sendToGpu(this);
}
this->internalEnd();
fCommandBufferInfo.fBounds.join(bounds);
}

View File

@ -22,28 +22,62 @@ class GrMtlPipelineState;
class GrMtlPipelineStateBuilder : public GrGLSLProgramBuilder {
public:
static GrMtlPipelineState* CreatePipelineState(GrRenderTarget*, GrSurfaceOrigin,
/**
* For Metal we want to cache the entire pipeline for reuse of draws. The Desc here holds all
* the information needed to differentiate one pipeline from another.
*
* The GrProgramDesc contains all the information need to create the actual shaders for the
* pipeline.
*
* For Metal we need to add to the GrProgramDesc to include the rest of the state on the
* pipeline. This includes blending information and primitive type. The pipeline is immutable
* so any remaining dynamic state is set via the MtlRenderCmdEncoder.
*/
class Desc : public GrProgramDesc {
public:
static bool Build(Desc*,
GrRenderTarget*,
const GrPrimitiveProcessor&,
const GrPipeline&,
GrPrimitiveType,
GrMtlGpu* gpu);
size_t shaderKeyLength() const { return fShaderKeyLength; }
private:
size_t fShaderKeyLength;
typedef GrProgramDesc INHERITED;
};
/** Generates a pipeline state.
*
* The GrMtlPipelineState implements what is specified in the GrPipeline and
* GrPrimitiveProcessor as input. After successful generation, the builder result objects are
* available to be used. This function may modify the program key by setting the surface origin
* key to 0 (unspecified) if it turns out the program does not care about the surface origin.
* @return true if generation was successful.
*/
static GrMtlPipelineState* CreatePipelineState(GrMtlGpu*,
GrRenderTarget*, GrSurfaceOrigin,
const GrPrimitiveProcessor&,
const GrTextureProxy* const primProcProxies[],
const GrPipeline&,
GrProgramDesc*,
GrMtlGpu*);
Desc*);
private:
GrMtlPipelineStateBuilder(GrRenderTarget*, GrSurfaceOrigin,
GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, GrSurfaceOrigin,
const GrPipeline&,
const GrPrimitiveProcessor&,
const GrTextureProxy* const primProcProxies[],
const GrPipeline&,
GrProgramDesc*, GrMtlGpu*);
GrProgramDesc*);
GrMtlPipelineState* finalize(const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,
Desc*);
const GrCaps* caps() const override;
GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
void finalizeFragmentOutputColor(GrShaderVar& outputColor) override;
void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override;
@ -53,7 +87,9 @@ private:
const SkSL::Program::Settings& settings,
GrProgramDesc* desc);
GrMtlPipelineState* finalize(const GrPrimitiveProcessor&, const GrPipeline&, GrProgramDesc*);
GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
GrGLSLVaryingHandler* varyingHandler() override { return &fVaryingHandler; }
GrMtlGpu* fGpu;
GrMtlUniformHandler fUniformHandler;

View File

@ -17,14 +17,14 @@
#import <simd/simd.h>
GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(
GrMtlGpu* gpu,
GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
const GrPrimitiveProcessor& primProc,
const GrTextureProxy* const primProcProxies[],
const GrPipeline& pipeline,
GrProgramDesc* desc,
GrMtlGpu* gpu) {
GrMtlPipelineStateBuilder builder(renderTarget, origin, primProc, primProcProxies, pipeline,
desc, gpu);
Desc* desc) {
GrMtlPipelineStateBuilder builder(gpu, renderTarget, origin, pipeline, primProc,
primProcProxies, desc);
if (!builder.emitAndInstallProcs()) {
return nullptr;
@ -32,12 +32,13 @@ GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(
return builder.finalize(primProc, pipeline, desc);
}
GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrMtlGpu* gpu,
GrRenderTarget* renderTarget,
GrSurfaceOrigin origin,
const GrPipeline& pipeline,
const GrPrimitiveProcessor& primProc,
const GrTextureProxy* const primProcProxies[],
const GrPipeline& pipeline,
GrProgramDesc* desc,
GrMtlGpu* gpu)
GrProgramDesc* desc)
: INHERITED(renderTarget, origin, primProc, primProcProxies, pipeline, desc)
, fGpu(gpu)
, fUniformHandler(this)
@ -316,7 +317,7 @@ uint32_t buffer_size(uint32_t offset, uint32_t maxAlignment) {
GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,
GrProgramDesc* desc) {
Desc* desc) {
auto pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
fVS.extensions().appendf("#extension GL_ARB_separate_shader_objects : enable\n");
@ -332,6 +333,7 @@ GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcess
settings.fSharpenTextures = fGpu->getContext()->priv().options().fSharpenMipmappedTextures;
SkASSERT(!this->fragColorIsInOut());
// TODO: Store shaders in cache
id<MTLLibrary> vertexLibrary = nil;
id<MTLLibrary> fragmentLibrary = nil;
vertexLibrary = this->createMtlShaderLibrary(fVS,
@ -354,6 +356,10 @@ GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcess
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(primProc);
pipelineDescriptor.colorAttachments[0] = create_color_attachment(this->config(), pipeline);
GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps();
pipelineDescriptor.stencilAttachmentPixelFormat =
pipeline.isStencilEnabled() ? mtlCaps->preferredStencilFormat().fInternalFormat
: MTLPixelFormatInvalid;
SkASSERT(pipelineDescriptor.vertexFunction);
SkASSERT(pipelineDescriptor.fragmentFunction);
@ -392,3 +398,35 @@ GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(const GrPrimitiveProcess
std::move(fFragmentProcessors),
fFragmentProcessorCnt);
}
//////////////////////////////////////////////////////////////////////////////
bool GrMtlPipelineStateBuilder::Desc::Build(Desc* desc,
GrRenderTarget* renderTarget,
const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,
GrPrimitiveType primitiveType,
GrMtlGpu* gpu) {
if (!INHERITED::Build(desc, renderTarget->config(), primProc,
GrPrimitiveType::kLines == primitiveType, pipeline, gpu)) {
return false;
}
GrProcessorKeyBuilder b(&desc->key());
int keyLength = desc->key().count();
SkASSERT(0 == (keyLength % 4));
desc->fShaderKeyLength = SkToU32(keyLength);
b.add32(renderTarget->config());
b.add32(renderTarget->numColorSamples());
b.add32(pipeline.isStencilEnabled() ? gpu->mtlCaps().preferredStencilFormat().fInternalFormat
: MTLPixelFormatInvalid);
// Stencil samples don't seem to be tracked in the MTLRenderPipeline
b.add32(pipeline.getBlendInfoKey());
b.add32((uint32_t)primitiveType);
return true;
}

View File

@ -9,6 +9,8 @@
#define GrMtlResourceProvider_DEFINED
#include "GrMtlCopyPipelineState.h"
#include "GrMtlPipelineStateBuilder.h"
#include "SkLRUCache.h"
#include "SkTArray.h"
#import <metal/metal.h>
@ -17,17 +19,67 @@ class GrMtlGpu;
class GrMtlResourceProvider {
public:
GrMtlResourceProvider(GrMtlGpu* gpu) : fGpu(gpu) {}
GrMtlResourceProvider(GrMtlGpu* gpu);
GrMtlCopyPipelineState* findOrCreateCopyPipelineState(MTLPixelFormat dstPixelFormat,
id<MTLFunction> vertexFunction,
id<MTLFunction> fragmentFunction,
MTLVertexDescriptor* vertexDescriptor);
GrMtlPipelineState* findOrCreateCompatiblePipelineState(
GrRenderTarget*, GrSurfaceOrigin,
const GrPipeline&,
const GrPrimitiveProcessor&,
const GrTextureProxy* const primProcProxies[],
GrPrimitiveType);
private:
#ifdef SK_DEBUG
#define GR_PIPELINE_STATE_CACHE_STATS
#endif
class PipelineStateCache : public ::SkNoncopyable {
public:
PipelineStateCache(GrMtlGpu* gpu);
~PipelineStateCache();
GrMtlPipelineState* refPipelineState(GrRenderTarget*, GrSurfaceOrigin,
const GrPrimitiveProcessor&,
const GrTextureProxy* const primProcProxies[],
const GrPipeline&,
GrPrimitiveType);
private:
enum {
// We may actually have kMaxEntries+1 PipelineStates in context because we create a new
// PipelineState before evicting from the cache.
kMaxEntries = 128,
};
struct Entry;
struct DescHash {
uint32_t operator()(const GrProgramDesc& desc) const {
return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0);
}
};
SkLRUCache<const GrMtlPipelineStateBuilder::Desc, std::unique_ptr<Entry>, DescHash> fMap;
GrMtlGpu* fGpu;
#ifdef GR_PIPELINE_STATE_CACHE_STATS
int fTotalRequests;
int fCacheMisses;
#endif
};
SkTArray<std::unique_ptr<GrMtlCopyPipelineState>> fCopyPipelineStateCache;
GrMtlGpu* fGpu;
// Cache of GrMtlPipelineStates
std::unique_ptr<PipelineStateCache> fPipelineStateCache;
};
#endif

View File

@ -9,10 +9,17 @@
#include "GrMtlCopyManager.h"
#include "GrMtlGpu.h"
#include "GrMtlPipelineState.h"
#include "GrMtlUtil.h"
#include "SkSLCompiler.h"
GrMtlResourceProvider::GrMtlResourceProvider(GrMtlGpu* gpu)
: fGpu(gpu) {
fPipelineStateCache.reset(new PipelineStateCache(gpu));
}
GrMtlCopyPipelineState* GrMtlResourceProvider::findOrCreateCopyPipelineState(
MTLPixelFormat dstPixelFormat,
id<MTLFunction> vertexFunction,
@ -29,3 +36,92 @@ GrMtlCopyPipelineState* GrMtlResourceProvider::findOrCreateCopyPipelineState(
fGpu, dstPixelFormat, vertexFunction, fragmentFunction, vertexDescriptor));
return fCopyPipelineStateCache.back().get();
}
GrMtlPipelineState* GrMtlResourceProvider::findOrCreateCompatiblePipelineState(
GrRenderTarget* renderTarget, GrSurfaceOrigin origin,
const GrPipeline& pipeline, const GrPrimitiveProcessor& proc,
const GrTextureProxy* const primProcProxies[], GrPrimitiveType primType) {
return fPipelineStateCache->refPipelineState(renderTarget, origin, proc, primProcProxies,
pipeline, primType);
}
////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GR_PIPELINE_STATE_CACHE_STATS
// Display pipeline state cache usage
static const bool c_DisplayMtlPipelineCache{false};
#endif
struct GrMtlResourceProvider::PipelineStateCache::Entry {
Entry(GrMtlGpu* gpu, GrMtlPipelineState* pipelineState)
: fGpu(gpu)
, fPipelineState(pipelineState) {}
GrMtlGpu* fGpu;
std::unique_ptr<GrMtlPipelineState> fPipelineState;
};
GrMtlResourceProvider::PipelineStateCache::PipelineStateCache(GrMtlGpu* gpu)
: fMap(kMaxEntries)
, fGpu(gpu)
#ifdef GR_PIPELINE_STATE_CACHE_STATS
, fTotalRequests(0)
, fCacheMisses(0)
#endif
{}
GrMtlResourceProvider::PipelineStateCache::~PipelineStateCache() {
// TODO: determine if we need abandon/release methods
fMap.reset();
// dump stats
#ifdef GR_PIPELINE_STATE_CACHE_STATS
if (c_DisplayMtlPipelineCache) {
SkDebugf("--- Pipeline State Cache ---\n");
SkDebugf("Total requests: %d\n", fTotalRequests);
SkDebugf("Cache misses: %d\n", fCacheMisses);
SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ?
100.f * fCacheMisses / fTotalRequests :
0.f);
SkDebugf("---------------------\n");
}
#endif
}
GrMtlPipelineState* GrMtlResourceProvider::PipelineStateCache::refPipelineState(
GrRenderTarget* renderTarget,
GrSurfaceOrigin origin,
const GrPrimitiveProcessor& primProc,
const GrTextureProxy* const primProcProxies[],
const GrPipeline& pipeline,
GrPrimitiveType primType) {
#ifdef GR_PIPELINE_STATE_CACHE_STATS
++fTotalRequests;
#endif
// Get GrMtlProgramDesc
GrMtlPipelineStateBuilder::Desc desc;
if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, primProc, pipeline, primType,
fGpu)) {
GrCapsDebugf(fGpu->caps(), "Failed to build mtl program descriptor!\n");
return nullptr;
}
std::unique_ptr<Entry>* entry = fMap.find(desc);
if (!entry) {
// Didn't find an origin-independent version, check with the specific origin
desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
entry = fMap.find(desc);
}
if (!entry) {
#ifdef GR_PIPELINE_STATE_CACHE_STATS
++fCacheMisses;
#endif
GrMtlPipelineState* pipelineState(GrMtlPipelineStateBuilder::CreatePipelineState(
fGpu, renderTarget, origin, primProc, primProcProxies, pipeline, &desc));
if (nullptr == pipelineState) {
return nullptr;
}
entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu, pipelineState)));
return (*entry)->fPipelineState.get();
}
return (*entry)->fPipelineState.get();
}

View File

@ -374,23 +374,6 @@ GrVkPipelineState* GrVkPipelineStateBuilder::finalize(const GrStencilSettings& s
//////////////////////////////////////////////////////////////////////////////
uint32_t get_blend_info_key(const GrPipeline& pipeline) {
GrXferProcessor::BlendInfo blendInfo;
pipeline.getXferProcessor().getBlendInfo(&blendInfo);
static const uint32_t kBlendWriteShift = 1;
static const uint32_t kBlendCoeffShift = 5;
GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
uint32_t key = blendInfo.fWriteColor;
key |= (blendInfo.fSrcBlend << kBlendWriteShift);
key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
return key;
}
bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
GrRenderTarget* renderTarget,
const GrPrimitiveProcessor& primProc,
@ -415,7 +398,7 @@ bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
stencil.genKey(&b);
b.add32(get_blend_info_key(pipeline));
b.add32(pipeline.getBlendInfoKey());
b.add32((uint32_t)primitiveType);